SendInput hintergrund



  • Hallo,

    ich bin der meinung, dass ich ein sendinput auf ein nicht-vordergrund-fenster brauche...

    eigentlich hat mir postmessage weitaus besser gefallen, aber je nach keyhandling reicht das ja (bei shift/ctrl/...) nicht...

    also habe ich sendinput + setforeground genommen:

    handle = search_window();
    handle_old = get_foreground_window();
    
    if(handle != handle_old)
      set_foreground( handle );
    
    SendInput(...);
    SendInput(...);
    SendInput(...);
    SendInput(...);
    
    //Sleep(1000);
    
    if(handle != handle_old)
      set_foreground( handle_old );
    

    wenn ich das sleep drin lasse, funktioniert alles bestens.
    so bald ich das auskommentiere, liefert sendinput zwar die richtigen rückgabewerte, aber die tasten werden nicht gesendet.

    mein sendinput ist in nem objekt gekapselt: ctor keydown, dtor keyup.
    deshalb sieht es bei tastenkombinationen so aus, dass für jede taste einzeln sendinput aufgerufen wird.

    die frage ist jetzt natürlich offensichtlich:
    ich möchte das sleep wegbekommen. wie? oder mach ich was anderes falsch?

    namespace keys { enum type : int { /*...*/ }; }
    
    struct key_send
    {
    	key_send( keys::type key, bool manual_ctrl = false )
    	{
    		input.type = INPUT_KEYBOARD;
    		input.ki.time = 0;
    		input.ki.dwExtraInfo = 0;
    
    		input.ki.wVk = WORD(key);
    
    		if( key >= keys::A && key <= keys::Z || key == keys::SPACE || key >= keys::NUM_0 && key <= keys::NUM_9 )
    			input.ki.dwFlags = KEYEVENTF_SCANCODE;
    		else
    			input.ki.dwFlags = KEYEVENTF_EXTENDEDKEY;
    
    		input.ki.wScan = WORD(MapVirtualKey(UINT(key), MAPVK_VK_TO_VSC));
    
    		if( !manual_ctrl )
    			press();
    	}
    
    	void press()
    	{
    		if( down )
    			return;
    
    		send();
    	}
    	void release()
    	{
    		if( !down )
    			return;
    
    		input.ki.dwFlags |= KEYEVENTF_KEYUP;
    		send();
    	}
    
    	~key_send()
    	{
    		try
    		{
    			release();
    		}
    		catch (...)
    		{
    			;
    		}
    	}
    private:
    	void send()
    	{
    		auto events = SendInput( 1, &input, sizeof(input) );
    
    		if( events != 1 )
    			throw windows::make_exception( "SendInput", events );
    
    		down = !down;
    	}
    
    	bool down = false;
    	INPUT input;
    };
    

    aufruf sieht so aus:

    auto foreground_window = my::windows::window::get_foreground_window();
    
    if( foreground_window != window_handle )
    	my::windows::window::set_foreground_window( window_handle );
    
    switch( the_command )
    {
    	case command::asd1:
    	case command::asd2:
    	{
    		my::key_send t1( my::keys::SPACE );
    		break;
    	}
    	case command::asd3:
    	{
    		my::key_send t1( my::keys::CTRL_L );
    		my::key_send t2( my::keys::ARROW_LEFT );
    		break;
    	}
    	/*...*/
    }
    
    //Sleep(1000);
    
    if( foreground_window != window_handle )
    	my::windows::window::set_foreground_window( foreground_window );
    

    was die funktionen tun, sollte aus den namen ersichtlich werden...

    danke schon mal 🙂

    bb



  • Ginge

    while (window_handle != get_foreground_window())
      Sleep(1);
    

    ?
    Ist zwar immer noch ein Warten, aber nicht mehr fest eine bestimmte Zeit.



  • Th69 schrieb:

    Ginge

    while (window_handle != get_foreground_window())
      Sleep(1);
    

    ?
    Ist zwar immer noch ein Warten, aber nicht mehr fest eine bestimmte Zeit.

    Häh? ist doch was komplett anderes...
    falls du

    if( foreground_window != window_handle )
    {
     Sleep(1);
     my::windows::window::set_foreground_window( foreground_window );
    }
    

    gemeint hast, funktioniert das nicht.
    ansonsten macht dein vorschlag für mich leider gar keinen sinn.

    bb

    edit: falls vorm sendinput meinst: das ist nicht nötig und bringt nichts.



  • unskilled schrieb:

    wenn ich das sleep drin lasse, funktioniert alles bestens.
    so bald ich das auskommentiere, liefert sendinput zwar die richtigen rückgabewerte, aber die tasten werden nicht gesendet.

    Mich dünkt, dass der Empfänger solcher Fake-Key-Events erst einmal speziell darauf vorbereitet werden muss. Ich hatte auch mal ein ähnliches Verhalten, habe es dann aber mit einer Kombination aus AttachThreadInput und SetFocus in den Griff bekommen.



  • Andromeda schrieb:

    unskilled schrieb:

    wenn ich das sleep drin lasse, funktioniert alles bestens.
    so bald ich das auskommentiere, liefert sendinput zwar die richtigen rückgabewerte, aber die tasten werden nicht gesendet.

    Mich dünkt, dass der Empfänger solcher Fake-Key-Events erst einmal speziell darauf vorbereitet werden muss. Ich hatte auch mal ein ähnliches Verhalten, habe es dann aber mit einer Kombination aus AttachThreadInput und SetFocus in den Griff bekommen.

    für mich sieht es so aus, als ob das sendinput asynchron ist und erst abgearbeitet ist, wenn schon wieder das falsche fenster im vordergrund ist.

    {
     my::key_send t1( my::keys::CTRL_L );
     my::key_send t2( my::keys::ARROW_LEFT );
    }
    
    //kein sleep
    set_foreground( ... );
    

    daraus wird dann laut jetzigem test- und wissensstand:

    .key_down(ctrl)
    .key_down(left);
    .set_foreground(old_foreground);
    .key_up(left);
    .key_up(ctrl);

    mit dem sleep wurden die beiden sendinputs (key_up) vor dem set_foreground bearbeitet. dass das für den moment ganz schön ist, aber nicht die lösung sein kann, sollte offensichtlich sein...^^



  • unskilled schrieb:

    für mich sieht es so aus, als ob das sendinput asynchron ist ...

    Ist ja auch so. SendInput schreibt die Daten einfach in die gleiche Queue in der auch der Keyboard-Treiber schreibt. Wenn du das mit deiner Zielanwendung synchronisieren willst, dann musst du eine Art Handshake selbst implementieren. Windows hat ja so schicke Events bzw. EventPairs für LPC, die man mit WaitForSingleObject und so abfragen kann. Damit geht das.

    Ich habe lange nichts mehr mit WinApi gemacht. Ich fand es aber immer sehr spannend. Irgenwie kriegt man immer hin was man will, auch wenn das oft in üblen Hacks ausartet. 🙂



  • Es wäre mal interessant, wie das my::windows::window::set_foreground_window aussieht. Ein alleiniger Aufruf von SetForegroundWindow reicht nämlich nicht unbedingt aus...Was Andromeda schon angedeutet hat, habe ich vor zig Jahren mal so gelöst:

    class ThreadAttacher
    {
    public:
    	ThreadAttacher(HWND to)
    	{
    		pid_=GetWindowThreadProcessId(to,0);
    		if (_threadid!=pid_)
    		{
    			needtoattach_ = true;
    			AttachThreadInput(_threadid,pid_,true);
    		}
    	}
    	~ThreadAttacher()
    	{
    		if(needtoattach_)
    			AttachThreadInput(_threadid,pid_,false);
    	}
    private:
    	bool needtoattach_;
    	DWORD pid_;
    };
    
    HWND ForceForegroundWindow(HWND to)
    {
        if(!to) return nullptr;
        HWND from=GetForegroundWindow();
        if(!from)
    	{
            SetForegroundWindow(to);
            return nullptr;
        }
        if(to==from)
            return nullptr;
    	ThreadAttacher thdatt(to);
    	SetForegroundWindow(to);
    
        return from;
    }
    

    Und anschließend gesendet, das klappte soweit ich mich erinnere immer bestens, auch wenn ich es jetzt nicht nochmal getestet habe. Du kannst es ja mal ausprobieren, ist ja nur das Ersetzen von set_foreground durch ForceForegroundWIndow...



  • AttachThreadInput
    war es tatsächlich - ty 🙂

    bin mir aber nicht sicher, ob das jetzt tatsächlich schon reicht oder nur zufall ist, weil der thread_attacher genug takte schluckt und dann die keyboard-queue ohnehin leer ist...

    quick und dirty siehts so aus:
    da ich sicher bin, dass from != to und weder der eine noch der andere nen system-handle ist, kann ich mir den ganzen schnick-schnack sparen^^

    HWND window_handle = /**/;
    
    auto foreground_window = ::GetForegroundWindow();
    
    if( foreground_window != window_handle )
      ::SetForegroundWindow( window_handle );
    
    { 
     key_send t1( my::keys::CTRL_L ); 
     key_send t2( my::keys::ARROW_LEFT ); 
    } 
    
    if( foreground_window != window_handle )  
    {
      thread_attacher att( foreground_window, window_handle );
      ::SetForegroundWindow( foreground_window );
    }
    

    mit

    class thread_attacher
    {
    public:
    	thread_attacher(HWND hwnd_from, HWND hwnd_to)
    	{
    		from = GetWindowThreadProcessId(hwnd_from, 0);
    		to = GetWindowThreadProcessId(hwnd_to, 0);
    
    		AttachThreadInput(from, to, true);
    	}
    	~thread_attacher()
    	{
    		AttachThreadInput(from, to, false);
    	}
    private:
    	DWORD from;
    	DWORD to;
    };
    

    rest wie oben



  • dref schrieb:

    Was Andromeda schon angedeutet hat, habe ich vor zig Jahren mal so gelöst:

    Irgendwie waren viele vor zig Jahren intime WinApi-Kenner, oder allgemein sehr in Microsoft-Technologien involviert. Inzwischen hat das sehr nachgelassen. Man nutzt zwar immer noch Windows im Alltag (aus Gewohnheit?), aber entwickelt wird meistens für andere Systeme.



  • ich noch mal...
    kurzfassung: danke schon mal; reicht aber noch nicht.

    ich schrieb:

    bin mir aber nicht sicher, ob das jetzt tatsächlich schon reicht oder nur zufall ist, weil der thread_attacher genug takte schluckt und dann die keyboard-queue ohnehin leer ist...

    das ist wohl tatsächlich der fall...

    wenn ich jetzt den code zusammen suche, sieht das in etwa so aus:

    struct scoped_foreground
    {
    	using hwnd_t = my::windows::window::handle_t; //typedef auf HWND
    
    	scoped_foreground( hwnd_t handle )
    	:	old_foreground( my::windows::window::get_foreground_window() )
    	,	new_foreground( handle )
    	{
    		my_assert( handle != my::windows::window::no_handle && "bad handle" );
    
    		if( old_foreground != new_foreground )
    			my::windows::window::set_foreground_window( new_foreground );
    	}
    
    	~scoped_foreground()
    	{
    		if( old_foreground == new_foreground )
    			return;
    
    		my::windows::window::thread_attacher attacher( old_foreground, new_foreground );
    		my::windows::window::set_foreground_window( old_foreground );
    	}
    
    private:
    	hwnd_t old_foreground;
    	hwnd_t new_foreground;
    };
    
    inline void set_foreground_window(handle_t hwnd)
    {
    	::SetForegroundWindow(hwnd);
    }
    
    inline handle_t get_foreground_window()
    {
    	return ::GetForegroundWindow();
    }
    

    und

    class thread_attacher
    {
    public:
    	thread_attacher(handle_t hwnd_from, handle_t hwnd_to)
    	{
    		from = ::GetWindowThreadProcessId(hwnd_from, 0);
    		to = ::GetWindowThreadProcessId(hwnd_to, 0);
    
    		::AttachThreadInput(from, to, true);
    	}
    	~thread_attacher()
    	{
    		::AttachThreadInput(from, to, false);
    	}
    private:
    	DWORD from;
    	DWORD to;
    };
    

    mir ist relativ klar, was wieso passiert, aber nicht so recht, wie es richtig geht... find auch so relativ wenig informationen zu dem thema - ist wohl auch ein nicht sonderlich verbreiteter anwendungsfall...

    zusammenfassung:
    in scoped_foreground::~scoped_foreground lebt thread_attacher .
    es scheint so, als ob beim ersten aufruf nach {zeitspanne x} die dtor-zeit nicht ausreicht, um die keyboard-queue abzuarbeiten. und deshalb ist die taste nach thread_attacher::~thread_attacher wohl noch in der queue...
    da ich aber keine kontrolle über die zielanwendung habe, weiß ich da absolut nicht weiter. sonst hätte ich es nicht über keysends gelöst 😉

    anwendungsfall im speziellen:

    void foo()
    {
      auto window_handle = /*...*/ ;
    
      scoped_foreground focus( window_handle );
      {
        key_send t1( tastencode );
      }
    }
    

    mit

    //todo: down/send
    
    struct key_send
    {
    	key_send( keys::type key, bool manual_ctrl = false )
    	{
    		input.type = INPUT_KEYBOARD;
    		input.ki.time = 0;
    		input.ki.dwExtraInfo = 0;
    
    		input.ki.wVk = WORD(key);
    
    		if( key >= keys::A && key <= keys::Z || key == keys::SPACE || key >= keys::NUM_0 && key <= keys::NUM_9 )
    			input.ki.dwFlags = KEYEVENTF_SCANCODE;
    		else
    			input.ki.dwFlags = KEYEVENTF_EXTENDEDKEY;
    
    		input.ki.wScan = WORD(MapVirtualKey(UINT(key), MAPVK_VK_TO_VSC));
    
    		if( !manual_ctrl )
    			press();
    	}
    
    	void press()
    	{
    		if( down )
    			return;
    
    		send();
    	}
    	void release()
    	{
    		if( !down )
    			return;
    
    		input.ki.dwFlags |= KEYEVENTF_KEYUP;
    		send();
    	}
    
    	~key_send()
    	{
    		try
    		{
    			release();
    		}
    		catch (...)
    		{
    			;
    		}
    	}
    private:
    	void send()
    	{
    		auto events = SendInput( 1, &input, sizeof(input) );
    
    		if( events != 1 )
    			throw windows::make_exception( "SendInput", events );
    
    		down = !down;
    	}
    
    	bool down = false;
    	INPUT input;
    };
    

    bb 🤡



  • compilierbares "minimal"bsp.:
    (sendet "Hey" and nen fenster, dessen Name mit " - Editor" endet. das sollte bei nem deutschen windows notepad sein)

    ->
    notepad öffnen
    compilieren/ausführen
    <-

    #include <windows.h>
    #include <stdexcept>
    
    namespace keys
    {
    	enum type : int
    	{
    		A = 'A', B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
    		NUM_0 = '0', NUM_1, NUM_2, NUM_3, NUM_4, NUM_5, NUM_6, NUM_7, NUM_8, NUM_9,
    
    		SPACE = VK_SPACE,
    
    		SHIFT = VK_SHIFT,
    		/*...*/
    		CTRL_L = VK_LCONTROL,
    
    		nothing = 0
    	};
    }
    
    struct key_send
    {
    	key_send(keys::type key, bool manual_ctrl = false)
    	{
    		input.type = INPUT_KEYBOARD;
    		input.ki.time = 0;
    		input.ki.dwExtraInfo = 0;
    
    		input.ki.wVk = WORD(key);
    
    		if (key >= keys::A && key <= keys::Z || key == keys::SPACE || key >= keys::NUM_0 && key <= keys::NUM_9)
    			input.ki.dwFlags = KEYEVENTF_SCANCODE;
    		else
    			input.ki.dwFlags = KEYEVENTF_EXTENDEDKEY;
    
    		input.ki.wScan = WORD(MapVirtualKey(UINT(key), MAPVK_VK_TO_VSC));
    
    		if (!manual_ctrl)
    			press();
    	}
    
    	void press()
    	{
    		if (down)
    			return;
    
    		send();
    	}
    	void release()
    	{
    		if (!down)
    			return;
    
    		input.ki.dwFlags |= KEYEVENTF_KEYUP;
    		send();
    	}
    
    	~key_send()
    	{
    		try
    		{
    			release();
    		}
    		catch (...)
    		{
    			;
    		}
    	}
    private:
    	void send()
    	{
    		auto events = SendInput(1, &input, sizeof(input));
    
    		if (events != 1)
    			throw std::runtime_error("SendInput");
    
    		down = !down;
    	}
    
    	bool down = false;
    	INPUT input;
    };
    
    #include <vector>
    #include <cassert>
    #include <cstring>
    
    namespace window
    {
    	using handle_t = HWND;
    	const handle_t no_handle = handle_t(0);
    
    	class thread_attacher
    	{
    	public:
    		thread_attacher(handle_t hwnd_from, handle_t hwnd_to)
    		{
    			from = ::GetWindowThreadProcessId(hwnd_from, 0);
    			to = ::GetWindowThreadProcessId(hwnd_to, 0);
    
    			::AttachThreadInput(from, to, true);
    		}
    		~thread_attacher()
    		{
    			::AttachThreadInput(from, to, false);
    		}
    	private:
    		DWORD from;
    		DWORD to;
    	};
    
    	inline void set_foreground_window(handle_t hwnd)
    	{
    		::SetForegroundWindow(hwnd);
    	}
    
    	inline handle_t get_foreground_window()
    	{
    		return ::GetForegroundWindow();
    	}
    
    	namespace detail
    	{
    		struct find_window_data
    		{
    			find_window_data(const char* title)
    				: title(title)
    				, handle(0)
    			{}
    
    			const char* title;
    			HWND handle;
    		};
    		struct wildcard_string
    		{
    			wildcard_string(const char* string)
    				: str(string)
    			{
    				if (std::strlen(str) > 1)
    					assert(!std::strchr(str + 1, '*') && "TODO");
    			}
    
    			bool operator== (const char* rhs) const
    			{
    				const char* lhs = str;
    
    				if (*lhs == '*')
    					return std::strstr(rhs, ++lhs) != nullptr;
    
    				return std::strcmp(lhs, rhs) == 0;
    			}
    			bool operator!= (const char* rhs) const
    			{
    				return !(*this == rhs);
    			}
    		private:
    			const char* str;
    		};
    
    		inline std::vector<char> get_title_from_hwnd(HWND hWnd)
    		{
    			const std::size_t MAX_LEN = 2048;
    
    			std::vector<char> ret_val(MAX_LEN);
    			int length = MAX_LEN - 1;
    
    			auto size_copyed = GetWindowTextA(hWnd, &ret_val[0], length);
    
    			ret_val.resize(static_cast<std::size_t>(size_copyed < 0 ? 0 : size_copyed + 1), '\0');
    			return ret_val;
    		}
    
    		inline BOOL CALLBACK find_window_impl(HWND hWnd, LPARAM lParam)
    		{
    			const BOOL call_next = TRUE;
    			const BOOL finished = FALSE;
    
    			find_window_data* data = reinterpret_cast<find_window_data*>(lParam);
    			assert(data && "invalid pointer");
    
    			std::vector<char> this_title = get_title_from_hwnd(hWnd);
    			if (this_title.empty())
    				return call_next;
    
    			wildcard_string lhs(data->title);
    			const char* rhs = &this_title[0];
    
    			if (lhs != rhs)
    				return call_next;
    
    			data->handle = hWnd;
    			return finished;
    		}
    	}
    
    	// Returns the window handle when found. if it returns 0 GetLastError() will return more information
    	// title may contain '*' as wildcard for [0, n] characters
    	inline handle_t find_window(const char* title)
    	{
    		assert(title && "invalid pointer");
    
    		detail::find_window_data data(title);
    		if( EnumWindows(detail::find_window_impl, reinterpret_cast<LPARAM>(&data)) )
    		{
    			SetLastError(ERROR_FILE_NOT_FOUND);
    			return no_handle;
    		}
    
    		SetLastError(ERROR_SUCCESS);
    		return data.handle;
    	}
    }
    
    struct scoped_foreground
    {
    	using hwnd_t = window::handle_t;
    
    	scoped_foreground(hwnd_t handle)
    	:	old_foreground( window::get_foreground_window() )
    	,	new_foreground( handle )
    	{
    		assert(new_foreground != window::no_handle && "bad handle");
    
    		if( old_foreground != new_foreground )
    			window::set_foreground_window(new_foreground);
    	}
    
    	~scoped_foreground()
    	{
    		if (old_foreground == new_foreground)
    			return;
    
    		window::thread_attacher attacher(old_foreground, new_foreground);
    		window::set_foreground_window(old_foreground);
    	}
    
    private:
    	hwnd_t old_foreground;
    	hwnd_t new_foreground;
    };
    
    #include <iostream>
    
    void real_main()
    {
    	auto notepad_handle = window::find_window("* - Editor");
    
    	if(notepad_handle == window::no_handle)
    		throw std::runtime_error("no window found");
    
    	scoped_foreground focus( notepad_handle );
    	{
    		{
    			key_send t1(keys::SHIFT);
    			key_send t2(keys::H);
    		}
    		key_send t3(keys::E);
    		key_send t4(keys::Y);
    	}
    
    	Sleep(1000); // !
    }
    
    int main()
    {
    	try
    	{
    		real_main();
    	}
    	catch (const std::exception& e)
    	{
    		std::cerr << e.what() << std::endl;
    	}
    
    	std::cout << "fertig" << std::endl;
    	char x;
    	std::cin >> x;
    }
    

Anmelden zum Antworten