Привет
Пишу приложение-переключатель раскладок, "висящее в трее".
Идея его в том, что
1) при наступлении определенного события (например, нажатия специальной клавиши), мое tray-приложение должно переключить раскладку активного в данный момент чужого приложения на заданную в конфиге раскладку.
2) Мое tray-приложение также мониторит события изменения раскладки других окон, но вызванные системой, а не самим tray-приложением (например, пользователь нажал Alt+Shift, или сменил активное окно — у которого была установлена другая раскладка) — и tray-программа должна выполнить какое-то действие. Т.е., мониторится любые изменения языка в Language Bar, кроме тех, которые были вызваны моей программой посылкой WM_LANGCHANGEREQUEST.
задачу 1 решил, просто посылаю окну, хэндл которого возвращает GetForegroundWindow(), событие WM_LANGCHANGEREQUEST с кодом нужной раскладки.
задачу 2 пока не решил. Вот здесь советуют перехватывать событие изменения текущего окна, и, при наступлении такого события, устанавливать локально в поток активного окна ловушку оконных сообщений на WM_INPUTLANGCHANGE, чтобы дополнительно словить все Alt+Shift (или мышкой) переключения языка.
Я так и рассчитывал сделать, но есть 2 вопроса:
1. Tray-приложение не должно получать уведомления об изменениях языка, если эти изменения были вызваны tray-приложением посылкой WM_LANGCHANGEREQUEST активному окну. Как узнать, что язык поменялся моей tray-программой, и игнорировать такое событие?
2. Если tray-приложение 32-битный процесс, будут ли проблемы с установкой локального хука в 64-битный процесс?
Здравствуйте, Michaels1, Вы писали:
M>2. Если tray-приложение 32-битный процесс, будут ли проблемы с установкой локального хука в 64-битный процесс?
Для ваших целей нет необходимости устанавливать хуки. Достаточно запустить обычный оконный таймер (WM_TIMER) с частотой примерно 10 раз/сек и воспользоваться тремя ф-циями:
вот кусок из работающей программы, которая делает именно то, что вы описали: переключает раскладки, а когда переключается язык, отображает индикатор текущего языка.
Чем хорош обработчик на таймере — он отлавливает не только переключение раскладки в текущем окне, но и смену текущего окна. А т.к. в Windows каждое приложение имеет свою текущую раскладку (вернее, в каждом UI потоке одного приложения раскладка независима), то при смене окна может измениться раскладка.
Единственная проблема — этот код не срабатывает в консольных окнах.
Здравствуйте, os24ever, Вы писали:
S>>Достаточно запустить обычный оконный таймер (WM_TIMER) с частотой примерно 10 раз/сек
O>Убивать, убивать, убивать, убивать, убивать, ржавой секироЙ УЖОСА ВО ИМЯ ДОБРА!!!!111пыщпыщ
O>Я всё сказал.
Зачем убивать-то!?! Не стоит! Достаточно поставить WM_TIMER с частотой хотя бы "писят" раз/сек, а еще лучше "пицот" раз/сек.... И он сам памрётъ!
Здравствуйте, Michaels1, Вы писали:
M>Привет M>Пишу приложение-переключатель раскладок, "висящее в трее". M>Идея его в том, что M>1) при наступлении определенного события (например, нажатия специальной клавиши), мое tray-приложение должно переключить раскладку активного в данный момент чужого приложения на заданную в конфиге раскладку. M>2) Мое tray-приложение также мониторит события изменения раскладки других окон, но вызванные системой, а не самим tray-приложением (например, пользователь нажал Alt+Shift, или сменил активное окно — у которого была установлена другая раскладка) — и tray-программа должна выполнить какое-то действие. Т.е., мониторится любые изменения языка в Language Bar, кроме тех, которые были вызваны моей программой посылкой WM_LANGCHANGEREQUEST. M>задачу 1 решил, просто посылаю окну, хэндл которого возвращает GetForegroundWindow(), событие WM_LANGCHANGEREQUEST с кодом нужной раскладки. M>задачу 2 пока не решил. Вот здесь советуют перехватывать событие изменения текущего окна, и, при наступлении такого события, устанавливать локально в поток активного окна ловушку оконных сообщений на WM_INPUTLANGCHANGE, чтобы дополнительно словить все Alt+Shift (или мышкой) переключения языка. M>Я так и рассчитывал сделать, но есть 2 вопроса: M>1. Tray-приложение не должно получать уведомления об изменениях языка, если эти изменения были вызваны tray-приложением посылкой WM_LANGCHANGEREQUEST активному окну. Как узнать, что язык поменялся моей tray-программой, и игнорировать такое событие? M>2. Если tray-приложение 32-битный процесс, будут ли проблемы с установкой локального хука в 64-битный процесс?
M>Спасибо
Все просто:
1) SetWindowsHookEx + WH_SHELL + HSHELL_LANGUAGE — получаем все извещения о смене языка.
2) SetWindowsHookEx + WH_CBT + HCBT_ACTIVATE|HCBT_SETFOCUS — получаем все переключения между окнами.
3) Как узнать что язык сменился самой своей программой?
А зачем узнавать-то? Какая разница кто сменил, система, Вы, другое чудное поделие? Какая собственно разница? По идее что-то нужно обновить в своем приложении (иконку) — дык и обновляйте в обработчиках, указанных выше (п.1 и п.2)
В конце концов, если так очень надо знать, то перед тем как вы меняете язык из своего приложения, кто мешает взвести какой-нить флаг у себя в программе? Только лишнее это. а) может сообщение не дойдет б) может язык не изменится в целевом приложении в) может еще какое поделие посредь вашей смены языка и еще раз сменит язык? Так что надежнее получать изменение языка именно когда об этом скажет само целевое приложение.
Здравствуйте, sergeyt4, Вы писали:
S>Чем хорош обработчик на таймере — он отлавливает не только переключение раскладки в текущем окне, но и смену текущего окна. А т.к. в Windows каждое приложение имеет свою текущую раскладку (вернее, в каждом UI потоке одного приложения раскладка независима), то при смене окна может измениться раскладка.
В первом сообщении я привел ссылку, где описывается, как отследить смену окна. Т.е. это не проблема. Таймер не хотелось бы использовать..
Основной вопрос — как игнорировать события изменения раскладки, вызванные моим tray-приложением? Можно ли как-нибудь "пометить" PostMessage WM_INPUTLANGCHANGEREQUEST, которое tray приложение посылает активному в данный момент окну (чужому приложению), с тем, чтобы это активное окно, поток которого захучен на сообщение WM_INPUTLANGCHANGE, не вызвало хук-коллбек в моем Tray процессе для этого "поддельного" WM_INPUTLANGCHANGEREQUEST (а реагировало только на изменение раскладки системой/пользователем)?
Здравствуйте, Carc, Вы писали:
C>Все просто: C>1) SetWindowsHookEx + WH_SHELL + HSHELL_LANGUAGE — получаем все извещения о смене языка.
C>2) SetWindowsHookEx + WH_CBT + HCBT_ACTIVATE|HCBT_SETFOCUS — получаем все переключения между окнами.
Все так, но есть одно но. На Vista и выше хук сработает только для тех процессов,
чей integrity level равен или ниже нашего. Это значит, что если мы хотим ловить событие
переключения раскладки от любого приложения в системе, то нашу программу придется запускать
от имени администратора. Так что минусуете, по большому счету, зря. Я одно время тоже
пытался решить эту задачу универсальным способом — бился-бился, да в конце понял, что
лучше таймера с GetKeyboardLayout ничего нету.
Здравствуйте, okman, Вы писали:
O>Здравствуйте, Carc, Вы писали:
C>>Все просто: C>>1) SetWindowsHookEx + WH_SHELL + HSHELL_LANGUAGE — получаем все извещения о смене языка.
C>>2) SetWindowsHookEx + WH_CBT + HCBT_ACTIVATE|HCBT_SETFOCUS — получаем все переключения между окнами.
O>Все так, но есть одно но. На Vista и выше хук сработает только для тех процессов, O>чей integrity level равен или ниже нашего. Это значит, что если мы хотим ловить событие O>переключения раскладки от любого приложения в системе, то нашу программу придется запускать O>от имени администратора. Так что минусуете, по большому счету, зря. Я одно время тоже O>пытался решить эту задачу универсальным способом — бился-бился, да в конце понял, что O>лучше таймера с GetKeyboardLayout ничего нету.
Здравствуйте, Michaels1, Вы писали:
M>Здравствуйте, sergeyt4, Вы писали:
S>>Чем хорош обработчик на таймере — он отлавливает не только переключение раскладки в текущем окне, но и смену текущего окна. А т.к. в Windows каждое приложение имеет свою текущую раскладку (вернее, в каждом UI потоке одного приложения раскладка независима), то при смене окна может измениться раскладка.
M>В первом сообщении я привел ссылку, где описывается, как отследить смену окна. Т.е. это не проблема. Таймер не хотелось бы использовать.. M>Основной вопрос — как игнорировать события изменения раскладки, вызванные моим tray-приложением? Можно ли как-нибудь "пометить" PostMessage WM_INPUTLANGCHANGEREQUEST, которое tray приложение посылает активному в данный момент окну (чужому приложению), с тем, чтобы это активное окно, поток которого захучен на сообщение WM_INPUTLANGCHANGE, не вызвало хук-коллбек в моем Tray процессе для этого "поддельного" WM_INPUTLANGCHANGEREQUEST (а реагировало только на изменение раскладки системой/пользователем)?
Код установки хука на WM_INPUTLANGCHANGEREQUEST в студию...
Здравствуйте, Carc, Вы писали:
S>>>Достаточно запустить обычный оконный таймер (WM_TIMER) с частотой примерно 10 раз/сек
O>>Убивать, убивать, убивать, убивать, убивать, ржавой секироЙ УЖОСА ВО ИМЯ ДОБРА!!!!111пыщпыщ
O>>Я всё сказал. C>Зачем убивать-то!?! Не стоит! Достаточно поставить WM_TIMER с частотой хотя бы "писят" раз/сек, а еще лучше "пицот" раз/сек.... И он сам памрётъ!
C>А по сути согласен!
Хмм. Наскоко помню, таймер там единственный способ отловить момент. Это даже в msdn-e написано, так что не машите тут своей пилкой для ногтей, да?
Кстати, продам с потрохами за штуку баксов уже готовую такую программу(исходники), с треем, подсвечивалкой, ремайндерами и переключателями. В этом месяце скачано 200+ раз.
Как этот тест-код работает:
Я устанавливаю хук на изменение текущего языка (когда язык в системе меняется — мигает Scroll lock), и устанавливаю таймер, который меняет язык текущего языка.
Так вот: Scroll lock мигает каждую секунду. А не хотелось бы — нужно чтоб скролл лок менял состояние только когда язык меняется ОС/пользователем, а не моим приложением.
Зачем мне это нужно: я пишу программу которая, по нажатию Scroll lock ON, должна выставить язык1, по нажатию Scroll lock OFF — язык2, при изменении тек. языка пользователем (Ctrl+Shift, Alt+Shift) — зажечь Scroll lock если выбранный язык принадлежит множеству {язык3, язык5, язык7, ...}, и погасить Scroll lock если принадлежит другому множеству {язык4, язык6, язык8, ...}.
Вот такое странное ТЗ мне дали
Поэтому мне и нужно знать в хук-коллбек-процедуре, что язык поменялся моим приложением или кем-то другим...
Как такое реализовать?
(Как я понял после экспериментов, для корректной работы такого в 64-битных системах нужно создать еще 64-битную программку (который установит хук в 64-битные процессы) и 64-битную dll, в дополнение к существующим 32-битным версиям..)
Здравствуйте, CEMb, Вы писали:
CEM>Хмм. Наскоко помню, таймер там единственный способ отловить момент. Это даже в msdn-e написано, так что не машите тут своей пилкой для ногтей, да?
Где именно написано?
Таймер — источник race conditions в моем случае..
Здравствуйте, Michaels1, Вы писали:
CEM>>Хмм. Наскоко помню, таймер там единственный способ отловить момент. Это даже в msdn-e написано, так что не машите тут своей пилкой для ногтей, да?
M>Где именно написано? M>Таймер — источник race conditions в моем случае..
Не нашёл, значит я наврал
Вобщем, н лет назад мы с какого-то примера сдули вариант с опросом по таймеру. Я думал, что с мсдн-овского.
А так таймер — да, самое плохое решение. Но в данном случае небольшой опрос нормально подходил для решения задачи.
Здравствуйте, CEMb, Вы писали:
CEM>Здравствуйте, Carc, Вы писали:
S>>>>Достаточно запустить обычный оконный таймер (WM_TIMER) с частотой примерно 10 раз/сек
O>>>Убивать, убивать, убивать, убивать, убивать, ржавой секироЙ УЖОСА ВО ИМЯ ДОБРА!!!!111пыщпыщ
O>>>Я всё сказал. C>>Зачем убивать-то!?! Не стоит! Достаточно поставить WM_TIMER с частотой хотя бы "писят" раз/сек, а еще лучше "пицот" раз/сек.... И он сам памрётъ!
C>>А по сути согласен!
CEM>Хмм. Наскоко помню, таймер там единственный способ отловить момент. Это даже в msdn-e написано, так что не машите тут своей пилкой для ногтей, да?
Ой ли? Случаем ли в подразделе "Programming India Today"?
CEM>Кстати, продам с потрохами за штуку баксов уже готовую такую программу(исходники), с треем, подсвечивалкой, ремайндерами и переключателями. В этом месяце скачано 200+ раз.
Переключалка раскладки в трее? А стандартная вроде бесплатно в упаковке и так есть? И напоминалки чего? Не забудь переключить раскладку?
Здравствуйте, CEMb, Вы писали:
CEM>Здравствуйте, Michaels1, Вы писали:
CEM>>>Хмм. Наскоко помню, таймер там единственный способ отловить момент. Это даже в msdn-e написано, так что не машите тут своей пилкой для ногтей, да?
M>>Где именно написано? M>>Таймер — источник race conditions в моем случае..
CEM>Не нашёл, значит я наврал CEM>Вобщем, н лет назад мы с какого-то примера сдули вариант с опросом по таймеру. Я думал, что с мсдн-овского. CEM>А так таймер — да, самое плохое решение. Но в данном случае небольшой опрос нормально подходил для решения задачи.
В данном случае? В случае топик-стартера? Постоянно работающая хелпер-софтина, которая трочитъ все декстопные приложения по несколько раз в секунду? Причем по барабану что там происходит, просмотр порнухи видео, проигрывание заставки Windows, отсутствие пользовательского ввода!?!
[поскипано]
M>(Как я понял после экспериментов, для корректной работы такого в 64-битных системах нужно создать еще 64-битную программку (который установит хук в 64-битные процессы) и 64-битную dll, в дополнение к существующим 32-битным версиям..)
Да, нужно создавать.
М-дя, мутный какой-то код... Зачем вообще хук-процедуру экспортировать из DLL? Для установки хука вполне достаточно системе дать ее адрес, а торчать наружу для вызова ей необходимости нет.
Ну и потом, когда язык меняется приложением можно банально взвести какой-нибудь флаг в DLL, и при получении управления в хуковой процедуре проверять этот флаг, если взведен, то соответственно ничего не делать со скролл-локом.
Только мутно это все и багообразующе капитально. ДЛЛ будет грузиться во все процессы, и каждый раз этот флаг будет находиться в чужом адресном пространстве. Соответственно, нужно шарить эту память между процессами. А это уже еще интереснее, в любом момент любой поток любого процесса по сути сможет попробовать получить доступ к этому флагу. Нужна будет синхронизация...
Тяжелый вариант получится — шаг в сторону\шаг на месте и здравствуй трудноуловимые баги. Как вариант можно попробовать переложить функционал манипулирования скролл-локом на свой exe. И соответственно из хуковой процедуры как-то передавать ему управление (чего-нить вроде WM_COPYDATA, или взводить какой-нить эвент из DLL, который будет ожидаться в exe).
В общем я бы переделал дизайн в целом... Отчасти я нечто подобное делал в Aml Maple. Там именно так сделано, некоторые вещи всегда выполняет именно exe-приложение, DLL только смотрит. Не скажу что верх идеала, чего уж тут. Но поскольку это не самая популярная возможность, а задач и так с головой хватает, то этот функционал так и оставил пока. DLL смотрит за языком, когда нужно оповещает exe, он решает что и когда делать. Тупо, в лоб, но работает. Для третьесортной возможности пока сойдет. Ну а когда время выдается, пишутся новые варианты толковее реализованные, но это больше для души и желания поковыряться в поисках красивого решения. До релизного варианта так ничего и не доводил пока.
Здравствуйте, Carc, Вы писали:
CEM>>А так таймер — да, самое плохое решение. Но в данном случае небольшой опрос нормально подходил для решения задачи. C>В данном случае? В случае топик-стартера? Постоянно работающая хелпер-софтина, которая трочитъ все декстопные приложения по несколько раз в секунду? Причем по барабану что там происходит, просмотр порнухи видео, проигрывание заставки Windows, отсутствие пользовательского ввода!?!
Зачем все? Ему же тока активное надо? И раз в секунду — нормально, не квейк же.
Да и уже фигня-вопрос, твой вариант
более нормальный, просто когда писали коран мою программу, я тогда ещё про этот шелловский хук не знал ничего Единственное, чё добавлю: можно это сделать без хука, подписавшись
на событие. Тогда 86vs64 + dll сторонние — по барабану, что сильно упрощает жизнь.
Btw, вон та фигня, в трее, которая язык(раскладку) показывает, на самом деле напрямик встраивается в процесс текущего окна. Т.е. вон то малекнькое окошко TF_FloatingLangBar_WndTitle — оно прям в текущем процессе. Узнал случайно, когда красил окна оно тоже вслед за процессом красилось.
Здравствуйте, CEMb, Вы писали:
CEM>Здравствуйте, Carc, Вы писали:
CEM>>>А так таймер — да, самое плохое решение. Но в данном случае небольшой опрос нормально подходил для решения задачи. C>>В данном случае? В случае топик-стартера? Постоянно работающая хелпер-софтина, которая трочитъ все декстопные приложения по несколько раз в секунду? Причем по барабану что там происходит, просмотр порнухи видео, проигрывание заставки Windows, отсутствие пользовательского ввода!?!
CEM>Зачем все? Ему же тока активное надо? И раз в секунду — нормально, не квейк же.
В принципе раз в секунду и диск форматировать то же нормально, главное щоб железка тянула...
Вопрос один, ЗАЧЕМ? Главное, зачем оно пользователю? Тут в принципе есть нормальный ответ, как ни странно. Если код который что-то там по таймеру более надежен и легок в сопровождении, то опять же, как ни странно, это пользователю надо. Хоть он об этом и не знает. Работает и решает проблемы пользователя — значь смысел езь! Но все ж, мы ж не просто пользователи? Имеет смысл и поискать решение.
Но никогда не забываем о старом архитектурном принципе: вопрос не выбора "А" или "Б". А какого фига вообще дизайн заставляет нас выбирать? И нельзя бы, чтобы всё было, а нам за это ничего не было оттянуть решение на попозжее? Ибо поздний выбор в архитектуре завсегда дешевше обходится (ибо информации как правило для выбора решения "А" или "Б" больше, и "чиста тетеретически", выбор правильнее.
Иногда можно и по таймеру, сам иногда балуюсь этим решением в одном вполне рабочем и живом проекте и именно про раскладку. Так что иногда можно и так. Вопрос только когда это "иногда" оправдано, а когда нет. У топик-стартера нет. Там задачка в принципе несложная: смена окна + смена языка. Ловится это и методами ловушек (хуки, подписка на события я в этом, несколько широком аспекте говорю).
CEM>Да и уже фигня-вопрос, твой вариант
более нормальный, просто когда писали коран мою программу, я тогда ещё про этот шелловский хук не знал ничего Единственное, чё добавлю: можно это сделать без хука, подписавшись
на событие. Тогда 86vs64 + dll сторонние — по барабану, что сильно упрощает жизнь.
Вот и я про то же. Иногда упрощение жизни стоит того, чтобы заставить лишний раз калькулятор впустую посчитать код раз в секунду. Но в то же время и на академический интерес с прибором класть не надо.
А шел-хук он непростой. Там был вариант и без DLL, а хук глобальный. Только undocumented он, вроде в 2К еще работал — дальше на память не вспомню. В общем там подумать стоит. В приниципе отдетектить доступность того или иного решения в ран-тайм несложно, можно на лету выбирать стратегию с DLL-хуком или глобальный хук в exe, пусть и undocumented.
CEM>Btw, вон та фигня, в трее, которая язык(раскладку) показывает, на самом деле напрямик встраивается в процесс текущего окна. Т.е. вон то малекнькое окошко TF_FloatingLangBar_WndTitle — оно прям в текущем процессе. Узнал случайно, когда красил окна оно тоже вслед за процессом красилось.
Ну это где как? В старых виндах по моему эта иконка отдельной жизнью жила, и работала аккурат через хуки.
Здравствуйте, Carc, Вы писали:
C>А шел-хук он непростой. Там был вариант и без DLL, а хук глобальный. Только undocumented он, вроде в 2К еще работал — дальше на память не вспомню. В общем там подумать стоит. В приниципе отдетектить доступность того или иного решения в ран-тайм несложно, можно на лету выбирать стратегию с DLL-хуком или глобальный хук в exe, пусть и undocumented.
Не, там совсем вроде без хука, хотя написано что это типа как хук(так и есть, но) там события посылаются мессаджами подписанному окну. Т.е. не надо писать отдельную функцию для хука. Я у себя эту штуку внедрил сразу, программа сразу стала легче и шустрее(та, которая в соседней ветке).
Здравствуйте, CEMb, Вы писали:
CEM>Здравствуйте, Carc, Вы писали:
C>>А шел-хук он непростой. Там был вариант и без DLL, а хук глобальный. Только undocumented он, вроде в 2К еще работал — дальше на память не вспомню. В общем там подумать стоит. В приниципе отдетектить доступность того или иного решения в ран-тайм несложно, можно на лету выбирать стратегию с DLL-хуком или глобальный хук в exe, пусть и undocumented.
CEM>Не, там совсем вроде без хука, хотя написано что это типа как хук(так и есть, но) там события посылаются мессаджами подписанному окну. Т.е. не надо писать отдельную функцию для хука. Я у себя эту штуку внедрил сразу, программа сразу стала легче и шустрее(та, которая в соседней ветке).
Я про это и говорю: там в общем сводилось всё что-то вроде RegisterWindowMessage ("Чего_то_Shell_Hook_дай_мне_зараза_строка_вроде") и получаем сообщения в оконную. Но!!!
1) Не уверен насчет шустрее. Глобальный хук будет дергаться в адресном пространстве захученного процесса. Здесь в адресном пространстве процесса поставившего хук. Со всеми вытекающими: переключения контекстов, побудка потока обрабатывающего сообщения и все дела. Но это так — чистая теория — не факт как это на деле реализовано. Может быть система просто где-то в недрах ставит нужное сообщение в очередь нашего окна, и лишних приседаний минимум.
2) Во вторых. Это undocumented. Я точно не помню где, ибо этим RegisterWindowMessage + Shell_Hook еще в назапамятном детстве баловался — но факт налицо. Где-то я встречался с тем что это undocumented feature не поддерживается. В какой-то там винде. Отсюда и все вероятно будущие проблемы. А в той же Мапле мне пофиг было, там по любому DLL была для других вещей нужна. Прицепить к ней отдельный функционал хука и не мучаться больше с undocumented было простым решением. Эти недокументированные вещи я юзал только в exe-файле, да и то это было в состоянии Research-функциоанала (там просто понадобилась другая архитектура наблюдатель\подписчик на события, причем наблюдатель только интерфейсом торчит наружу, поэтому легко пробовались разные способы. Но опять же это все в стадии пробы сил, проверки идей, не уверен что я бы решился на такую реализацию в релизном, законченном варианте).