Rozgłaszanie skrótu klawiszowego w systemie i przechwytywanie go przez inny program

0

Od jakiegoś czasu pracuję nad pewnym narzędziem. Jego zadaniem jest gromadzenie i generowanie różnych danych widocznych w jego głównym oknie, a także renderowanie wybranych informacji w oknie podglądu. Czyli program ma dwa okna – główne z kontrolkami i drugie z podglądem dynamicznie wyrenderowanych danych.

Ważną jego funkcją jest informowanie innych programów o zmianie zawartości tego drugiego okienka. Jeśli zawartość okna zmieni się, mój program symuluje określony skrót klawiszowy, który zewnętrzne programy muszą odebrać. Jest to wygodne i funkcjonalne rozwiązanie, dlatego że mój program może współpracować z dowolnym zewnętrznym programem (którego autorem nie jestem), a nawet z wieloma jednocześnie.


Do symulowania wciskania kombinacji klawiszy używam poniższej procedury. Skorzystałem z keybd_event, bo oczywiście importu funkcji SendInput, typów danych i flag brakuje w module Windows. :/

procedure BroadcastShortCut(AKey: UInt16; AShift: TShiftState);
begin
  if ssShift in AShift then keybd_event(VK_SHIFT,   MapVirtualKey(VK_SHIFT,   0), 0, 0);
  if ssAlt   in AShift then keybd_event(VK_MENU,    MapVirtualKey(VK_MENU,    0), 0, 0);
  if ssCtrl  in AShift then keybd_event(VK_CONTROL, MapVirtualKey(VK_CONTROL, 0), 0, 0);

  keybd_event(AKey, MapVirtualKey(AKey, 0), 0, 0);
  keybd_event(AKey, MapVirtualKey(AKey, 0), KEYEVENTF_KEYUP, 0);

  if ssCtrl  in AShift then keybd_event(VK_CONTROL, MapVirtualKey(VK_CONTROL, 0), KEYEVENTF_KEYUP, 0);
  if ssAlt   in AShift then keybd_event(VK_MENU,    MapVirtualKey(VK_MENU,    0), KEYEVENTF_KEYUP, 0);
  if ssShift in AShift then keybd_event(VK_SHIFT,   MapVirtualKey(VK_SHIFT,   0), KEYEVENTF_KEYUP, 0);
end;

Przetestowałem program w połączeniu z odtwarzaczem WinAMP, który posiada obsługę globalnych klawiszy – bez problemu mogę swoim programem np. przełączać utwory w tym odtwarzaczu. Tu jest wszystko w porządku.

Ale jest problem – program OBS nie reaguje na zasymulowane skróty klawiaturowe. Tzn. reaguje, ale tylko wtedy, gdy okno mojego programu jest nieaktywne i użytkownik swoimi palcami wstuka zadaną kombinację na klawiaturze. No nie może tak być – użytkownik ma obsługiwać mój program, więc jego okno siłą rzeczy musi być aktywne, natomiast zewnętrzne programy powinny działać w tle.


Przechodząc do sedna – w jaki sposób inaczej rozgłosić w systemie dany skrót klawiszowy, tak aby dowolne zewnętrzne programy mogły na ten skrót reagować?

0

@skrzat: niestety nie – podlinkowane rozwiązania wymagają pobrania uchwytu okna zewnętrznego programu, a ja przecież nie wiem z jakiego programu docelowo skorzysta użytkownik. Poza tym on może użyć wielu programów i one wszystkie mogą reagować na dany skrót jednocześnie. Dlatego jedynym sposobem jest rozgłoszenie w systemie odpowiedniej kombinacji. Tyle że nie wiem dlaczego WinAMP bez problemu reaguje, a OBS nie… :/


Myślałem o SendMessage z HWND_BROADCAST, ale nie wiem czy to zadziała. A nawet jeśli to najpierw musiałbym przerobić oba wejściowe parametry na liczbę dla wParam, a chwilowo nie mam pojęcia jak to zrobić.

Chyba że zamiast keybd_event po prostu puszczę seryjkę SendMessage z HWND_BROADCAST, najpierw z komunikatem WM_KEYDOWN a potem z WM_KEYUP, tak dla każdego klawisza z osobna. Choć nie wiem czy to dobry pomysł i czy programy nie zwariują od tego.


Edit: SendMessage odpada, nie działa to.

0

Przychodzi mi do głowy tylko takie toporne rozwiązanie :)

  1. W twoim programie jest lista wszystkich aktualnie otwartych okien programów
  2. Użytkownik wybiera jedno lub więcej okien
  3. Twój program wysyła skrót do tych wybranych okien

Czy jeśli okno OBS jest nieaktywne, to przyjmuje skróty z klawiatury? Bo może problem z OBS nie jest rozwiązywalny, jeśli okno nie jest aktywne.

0
skrzat napisał(a):
  1. W twoim programie jest lista wszystkich aktualnie otwartych okien programów
  2. Użytkownik wybiera jedno lub więcej okien
  3. Twój program wysyła skrót do tych wybranych okien

Można, choć znacząco komplikuje to niepotrzebnie dość prosty mechanizm.

Czy jeśli okno OBS jest nieaktywne, to przyjmuje skróty z klawiatury?

Powinien je przyjmować, dlatego że zapewne można ten program podczas działania zminimalizować do zasobnika, tak aby nie zajmował miejsca na ekranie podczas jego nagrywania. W teorii powinien on zakładać hooka na klawiaturę i przetwarzać wszystkie komunikaty klawiatury, ale nie wiem jak jest zaprojektowany.

1

Klawisze zawsze lecą do aktywnego okna, zawsze możesz zapamiętać aktywne okno, zaktywować odpowiednie okno, wysłać klawisze oraz odtworzyć zapamiętane okno.
Możesz też próbować bezpośrednio do okna wysyłać WM_KEYDOWN WM_KEYUP z tym że to też ma swoje wady, jeżeli to zrobione poprzez coś co nie tworzy okienka windowsowe oprócz głównego to też nie zadziała.
Ogólnie nie ma metody na to aby działało w każdym przypadku.

0
_13th_Dragon napisał(a):

Klawisze zawsze lecą do aktywnego okna, zawsze możesz zapamiętać aktywne okno, zaktywować odpowiednie okno, wysłać klawisze oraz odtworzyć zapamiętane okno.

W tym rzecz że nie mogę – użytkownik może skonfigurować wiele aplikacji, które jednocześnie mają na dany skrót zareagować. Nie wiem z jakiego zewnętrznego programu (lub wielu programów) skorzysta użytkownik, więc nie mogę bazować na szukaniu uchwytów okien.

Skoro OBS reaguje tylko wtedy, gdy okno mojego programu jest nieaktywne, to póki co obejściem tego problemu jest aktywowanie innego okna na czas wysyłania skrótów klawiszowych i ponowne aktywowanie okna po wysłaniu. Spróbuję w ten sposób.

2

wystarczy, że nie jest to globalny hook i już nie działa jeśli okno nie jest aktywne i tego nie przeskoczysz

0

Z otrzymanego od użytkownika feedbacku wiem tyle, że jeśli okno mojego programu jest aktywne to OBS nie reaguje:

feedback:

In my stream program (OBS) I've set up the hotkeys according to those that are simulated by CTCT when you switch to a scene in it. I'm however unable to use those hotkeys while CTCT is the active window. Both when CTCT sends out the simulated keypresses, but also when I press the hotkeys themselves, the scene doesn't switch while CTCT is the active window.

a jeśli jest nieaktywne to reaguje:

feedback:

However once I press the hotkeys while any other program is the active window, such as Discord, the hotkeys work fine and OBS switches the scene accordingly.

Czyli wychodzi na to, że OBS potrafi podsłuchać wciskane klawisze z innych programów z wyjątkiem mojego. :/

0

Z przeczytanych w sieci materiałów dowiedziałem się, że jest to dość popularny problem, jeśli chodzi o sterowanie OBS-em. Sporo ludzi ma z tym problem – różne ich programy symulują skróty klawiszowe i OBS nie reaguje na nie gdy ten jest zminimalizowany.

Wyczytałem też, że problemem może być to, że OBS działa na niższych uprawnieniach niż aplikacja wysyłająca skróty. Mój program (CTCT) musi działać z uprawnieniami administratora, więc możliwe, że mój ”klient” uruchomił OBS z podstawowymi uprawnieniami i przez to komunikacja nie działa. Drugim problemem może być to, że symulowanie wciskania klawiszy wykonywane jest zbyt szybko i OBS nie nadąża. Wielu sugeruje dodanie Sleep(10) po wysłaniu każdego klawisza wchodzącego w skład kombinacji.

Sprawdziłem tego Sleep(10) i WinAMP nadal reaguje bez problemu. Spróbuję w ten sposób – powinno zatrybić.

0

No nic, nawet po wprowadzonych zmianach (interwał i uprawnienia admina) OBS nadal nie jest w stanie odczytywać symulowanych skrótów klawiszowych, choć inne programy (np. Wondershare Filmora) nie mają z tym problemu. Najwyraźniej nie zakłada on globalnego hooka, więc… jego strata.

0

Mam taki głupi pomysł...
Odpalać każdą z programów co użytkownik zamierza obsługiwać w osobnym VirtualBox'e
W każdym z których tylko jedna aplikacją będzie pracować więc zawsze jest aktywna.
A do VBox'ów przekazujesz serię klawiszy przez OCX na przykład albo przez port.

0

Nie no to jest… bardzo dziwny pomysł. ;)

Mimo wszystko nie będę już niczego zmieniał w swoim programie, bo to mi się nie kalkuluje – dla jednego programu nie ma sensu stawać na głowie i tak kombinować. Problemem nie jest mój program, a OBS, więc to jego autorzy powinni poprawić swój produkt, aby zaczął działać prawidłowo i był w pełni funkcjonalny.

Zresztą problem z przechwytywaniem przez OBS klawiszy z innych programów jest dość powszechny, wiele osób pytało na forum dlaczego OBS nie odbiera klawiszy, a mimo to nic się w tym temacie nie zmieniło. Tak więc zgłaszanie im tego problemu raczej nie ma sensu, bo najpewniej i tak to oleją. A że konkurencja na runku nagrywarek ekranu jest spora, to użytkownicy mogą skorzystać z innej.

0

A prawie nic nie musisz zmieniać, jedynie zamiast odpalać program użytkownika jako:
exec(cmd);
Odpalasz:
RunMyVirtBox(cmd);
Oraz dodajesz wtyczki.

0

Ale to już informacje nie dla mnie, a dla użytkowników. :P

OBS-a można oskryptować, więc może by dało się w skrypcie założyć hooka, odbierać komunikaty i przesyłać takie informacje do głównego okna nagrywarki. Nie wiem czy to możliwe, ale nie używam tego programu więć nie mam skąd tego wiedzieć.

2

nie wiem dla jakiego czasu sleepsprawdzałeś, ale dla 50 działa (win10 x64):

procedure TForm1.RozpocznijNagrywanieClick(Sender: TObject);
begin
  keybd_event(VK_SHIFT, MapVirtualKey(VK_SHIFT, 0), 0, 0);
  keybd_event(VK_F7, MapVirtualKey(VK_F7, 0), 0, 0);
  sleep(50);
  keybd_event(VK_F7, MapVirtualKey(VK_F7, 0), KEYEVENTF_KEYUP, 0);
  keybd_event(VK_SHIFT, MapVirtualKey(VK_SHIFT, 0), KEYEVENTF_KEYUP, 0);
end;

procedure TForm1.ZatrzymajNagrywanieClick(Sender: TObject);
begin
  keybd_event(VK_SHIFT, MapVirtualKey(VK_SHIFT, 0), 0, 0);
  keybd_event(VK_F8, MapVirtualKey(VK_F8, 0), 0, 0);
  sleep(50);
  keybd_event(VK_F8, MapVirtualKey(VK_F8, 0), KEYEVENTF_KEYUP, 0);
  keybd_event(VK_SHIFT, MapVirtualKey(VK_SHIFT, 0), KEYEVENTF_KEYUP, 0);
end;      
0

@Paweł Dmitruk, Ale nadal aplikacja musi być aktywna, inaczej nie ona przyjmie te klawisze.

0
Paweł Dmitruk napisał(a):

nie wiem dla jakiego czasu sleepsprawdzałeś […]

Ja nie sprawdzałem – to użytkownik mi zgłosił, że do nagrywania obrazu skorzystał z OBS i że ten nie czyta klawiszy z mojego programu. Później wysłałem mu poprawioną wersję ze Sleep(10), ale to też nie zadziałało.

[…] ale dla 50 działa (win10 x64):

Mogę dać nawet więcej, np. Sleep(100) czy Sleep(200), jeśli tylko OBS zacznie reagować. Ok, sprawdzę to sobie, dziękuję za pomoc. :]

2

Okazało się, że problemem jako tako nie jest sam OBS (choć też) i sposób w jaki podsłuchuje zdarzenia klawiatury, a uprawnienia aplikacji i to, w jaki sposób system tym całym bajzlem zarządza. :/


Otóż jeśli CTCT został uruchomiony z prawami admina, to OBS nie był w stanie podsłuchiwać z niego klawiszy, nawet jeśli sam posiadał uprawnienia admina. Tzn. takie informacje otrzymałem w feedbacku od użytkownika, który w ten sposób próbował sparować te dwa programy.

Po zasymulowaniu skrótu klawiszowego przez mój program, np. kombinacji Shift+Ctrl+Alt+F1, inne programy nie posiadające uprawnień admina co prawda potrafiły ten skrót podsłuchać, ale tylko klawisze specjalne – klawisz F1 nie docierał. Wyłączenie wymuszania praw admina rozwiązało ten problem (pozbyłem się kodu zależnego od uprawnień – trudno). Najprawdopodobniej uruchomienie obu aplikacji z prawami admina problem z podsłuchiwaniem by rozwiązało, ale jest jeszcze jedna kwestia – o niej w dalszej części posta.

Do sprawdzania stanu klawiatury skorzystałem z programu KeyboardStateView – bardzo fajne narzędzie.


Mimo wszystko swój kod też musiałem zmienić, bo symulowanie wciskania i zwalniania klawiszy ciurkiem powodowało, że OBS nie nadążał z ich analizą. Inne programy nie były na to wrażliwe, więc widać OBS ssie. W każdym razie poprawne rozwiązanie tego problemu podał @Paweł Dmitruk – trzeba dorzucić Sleep pośrodku, aby dać szansę wspomnianej nagrywarce na reakcję:

procedure BroadcastShortCut(AKey: UInt16; AShift: TShiftState);
begin
  if ssShift in AShift then Windows.keybd_event(VK_SHIFT,   MapVirtualKey(VK_SHIFT,   0), 0, 0);
  if ssAlt   in AShift then Windows.keybd_event(VK_MENU,    MapVirtualKey(VK_MENU,    0), 0, 0);
  if ssCtrl  in AShift then Windows.keybd_event(VK_CONTROL, MapVirtualKey(VK_CONTROL, 0), 0, 0);

  Windows.keybd_event(AKey, MapVirtualKey(AKey, 0), 0, 0);
  Windows.Sleep(100);
  Windows.keybd_event(AKey, MapVirtualKey(AKey, 0), KEYEVENTF_KEYUP, 0);

  if ssCtrl  in AShift then Windows.keybd_event(VK_CONTROL, MapVirtualKey(VK_CONTROL, 0), KEYEVENTF_KEYUP, 0);
  if ssAlt   in AShift then Windows.keybd_event(VK_MENU,    MapVirtualKey(VK_MENU,    0), KEYEVENTF_KEYUP, 0);
  if ssShift in AShift then Windows.keybd_event(VK_SHIFT,   MapVirtualKey(VK_SHIFT,   0), KEYEVENTF_KEYUP, 0);
end;

Powyższy kod działa prawidłowo ze wszystkimi testowanymi programami, również z OBS.

1 użytkowników online, w tym zalogowanych: 0, gości: 1