Page 1 of 2

Replace text while typing

Posted: Fri Dec 26, 2008 10:51 am
by Lost
Hi,

I'm new to PureBasic (I only purchased a license the other week). Let me start by saying the PureBasic community seems to be fantastic. It is very helpful to tinker around with other people's code to get a feel of the language, so a big thank you to all who published code on the forum! I'm not really a programmer (the more advanced concepts are way beyond me) and only have limited scripting experience (VBS & AutoIt).

I am working on a little program that (when finished) will replace typed abbreviations with a blurb of text. For example, if I type 'ttyl' in my mail application, I want to replace that text with 'Talk to you later'. I know there are a few applications out there that do exactly what I'm trying to do (Texter comes to mind), but that's not the point.

The point is I just started and I'm already stuck (how typical)! What I'm trying to do is capture the keyboard input from all applications and check them against an array of abbreviations. If the application encounters a known abbreviation, it needs to change it to the predefined text immediately. I did a few searches on the forum and found some examples that relate to hotkeys, but I don't know if that's the way to go. I also found some postings about keyboard hooks, but I'm afraid that's a bit beyond me at the moment. Maybe someone on the forum can show me the door, so I can walk through it? 8)

Posted: Fri Dec 26, 2008 3:09 pm
by netmaestro
What you're asking isn't a project I'd recommend for a beginning programmer to tackle. A keyboard hook in a compiled dll is necessary, and in the dll you would:

- identify the stored abbreviation and replacement text
- identify the class of control the text was being typed into
- based on the control class, choose a method of text replacement, several will require COM
- replace the text

It's very doable but skill with API would be a prerequisite at minimum.

Posted: Fri Dec 26, 2008 10:16 pm
by Sparkie
Lost wrote:I also found some postings about keyboard hooks, but I'm afraid that's a bit beyond me at the moment.
freak has some how to hook code here http://www.purebasic.fr/english/viewtop ... 9460#79460 that should help. It's not updated for PB4.xx but you should have no trouble with that part of it.

http://msdn.microsoft.com/en-us/library/ms644990.aspx will show the various hooks and how to use them.

I agree with netmaestro. You've given yourself a HUGE challenge in that you are a PB beginner AND you are not experienced in using the API. The amount of code needed for this task is not lengthy nor is it difficult to learn, so don't be discouraged. 8)

Posted: Sat Dec 27, 2008 1:45 am
by PB
> A keyboard hook in a compiled dll is necessary

Not really. I just poll the keyboard state and build up a string of each typed
letter, and when the string matches a pre-defined one, I use my SendKeys
procedure to delete the string and type the new longer one. All without any
hooking or DLLs. And with 0% CPU usage too, before you ask. ;)

I agree it's not for beginners, which is why I'm not posting the code here,
as well as the fact that none of my apps are open-source. If the original
poster (Lost) wants to try it though: I use the GetAsyncKeyState_() API
command in addition to my SendKeys procedure here in the forums. It
may not sound like the right way to do it, but it works, and means virus
checkers don't crap themselves and declare my app to be spyware (like
some do when you use a hook to accomplish the same thing). Trust me,
I know. I wasted my time hooking and ditched it when users kept saying
their virus app was calling it spyware.

Posted: Sat Dec 27, 2008 2:49 am
by Lost
Thanks guys, I really appreciate your input. I looked at the links in Sparkie's post before and I think you guys are right, I should revisit the keyboard hooks a bit later when I'm more experienced.

Now the solution PB put on the table sounds very interesting. I'll definately have a look at that. I found a post about SendKeys a few days back and can remember thinking it was cool to have that functionality in PureBasic as I use it a lot in AutoIt. The funny thing is, Texter (http://lifehacker.com/software/texter/l ... 238306.php) was created using AutoHotkey and I bet it's using the same concept.

Again, thanks a lot guys!

Posted: Sat Dec 27, 2008 1:16 pm
by Trond
PB wrote:I just poll the keyboard state...
And how do you do that? If you're using GetAsyncKeyState_() then your solution is nice, simple, efficient, and wrong. If the user presses and releases the same key twice between two of your calls to GetAsyncKeyState_() then you'll lose the second key press/release.
If you don't use that function then I'd like to know what you're using.

Posted: Sat Dec 27, 2008 1:42 pm
by PB
> If you're using GetAsyncKeyState_() then your solution is nice, simple, efficient, and wrong

Well, I've been using it for 5 years successfully, so... whatever.

> If the user presses and releases the same key twice between two of your
> calls to GetAsyncKeyState_() then you'll lose the second key press/release

If you say so. Again, 5 years real-world use proves otherwise.

Posted: Sat Dec 27, 2008 8:48 pm
by Trond
3 minutes of coding and on first try I got it to drop keypresses. As long as a higher-priority thread uses a lot of cpu (or you have many same-priority threads which uses a lot of cpu) then your thread is starved, and even when it gets control back once in a while it won't process all the keys typed in between.

Keyboard watching code (watches "n" key only"):

Code: Select all

OpenWindow(0, 0, 0, 512, 384, "", $CF0001)
StringGadget(0, 10, 10, 490, 23, "")

Repeat
  Select WaitWindowEvent(100)
    Case #PB_Event_CloseWindow
      Break
  EndSelect
  If GetAsyncKeyState_(#VK_N) & 1
    SetGadgetText(0, GetGadgetText(0) + "n")
  EndIf
ForEver
Heavy thread code (run with debugger so you can kill it):

Code: Select all

Procedure thread(void)
  Repeat
  ForEver
EndProcedure

CreateThread(@thread(), 0)
CreateThread(@thread(), 0)

Delay(100000)
Of course, it's a fair way of doing it, just not correct (behaving differently under load, such as dropping keypresses is simply not correct).

Posted: Sun Dec 28, 2008 2:57 am
by PB
> 3 minutes of coding and on first try I got it to drop keypresses

So? Is that why it's "wrong" to do it with GetAsyncKeyState_()?
All it shows is that my method is superior to yours in this case.
And no, I don't use threads in my app. And yes, my code still
captures ALL keystrokes even with other CPU-heavy apps are
running (eg. I can play Doom3 and it records all my presses).
Been working fine that way for 5 years, as I need to re-state.

So please don't label something as "wrong" just because you
couldn't make it work on your first 3-minute try. It's misleading.

Posted: Sun Dec 28, 2008 4:36 am
by Sparkie
No right, no wrong...just keep your users happy. :)

Posted: Sun Dec 28, 2008 6:01 am
by JCV
Some keyloggers (spywares) use this way without hooking.

Posted: Sat Jan 03, 2009 7:19 pm
by Trond
PB wrote:All it shows is that my method is superior to yours in this case.
But I just coded it as I thought you said you did... It's your method that's failing. If you didn't code it this way then I'm very curious to know what you did.

Just because it haven't failed you doesn't mean it's right. For years an unlucky programmer used this code to change true into false and false into true:

Code: Select all

If Var = #True
  Var = #False
Else
  Var = #False
EndIf
See how it's obviously wrong? Still, after ten years and several thousand runs it had never failed once, because Var always happened to be #true. An extreme case, but it shows that your argument is wrong: that something hasn't failed yet, doesn't mean it's correct.

And no, I don't use threads in my app.
I meant threads from other apps.

Posted: Sun Jan 04, 2009 6:41 am
by PB
> Just because it haven't failed you doesn't mean it's right

Thank you for your input, but I must say I disagree with that comment.

Posted: Sun Jan 04, 2009 12:52 pm
by Seldon
I agree with Trond when he says the GetAsyncKeyState() method is not the correct way. I did a keylogger some years ago and made the hook DLL in assembly (using same sample found on Internet). The correct way to go is a 'system wide' DLL , even not a normal DLL. If you hook a normal DLL, each program will open an instance of that DLL (and you can waste a lot of memory). If you use a system wide DLL , only one instance of it is open. It is not possible at the moment with PB, but since the hook DLL must not do a lot of things, you can use C or assembly. For example my keylogger was made of two components: 1) the system wide DLL that only captures the keys and sends a private message with key code to the 2) the main program that simply waits all the time and saves the received key strokes on disk.
I'm sorry PB, but it seems to me you don't want to listen or accept this. It is not possible to get all the keys by using GetAsyncKeyState() when the CPU is very busy. And I hope your function doesn't use 100% of CPU time to avoid that. I've personally tried that method, Trond tried... and we're programmers, maybe your users haven't simply noticed that because they are not programmers or because it is not strictly important to capture any single key. If your program works... ok it's fine as long as it is doing what it has to do, but consider there are better ways.

Posted: Sun Jan 04, 2009 1:08 pm
by PB
> It is not possible to get all the keys by using GetAsyncKeyState() when
> the CPU is very busy

I'm honored to know that I can code the impossible then. ;)

> you don't want to listen or accept this

Again, my own app is all the proof I need. Your statements won't suddenly
make my app stop working and skip keystrokes, will they? :lol:

I know I probably sound smug, but hell guys... I've never missed a key yet
and neither have my users. And it's not a keylogger where a missed key
won't matter: it's a real-time word replacement thing, where every key has
to be accounted for, and it works. All with GetAsyncKeyState and all without
my app using 100% CPU. I honestly don't see what's so hard about it.

You know, just for shits and giggles (as the saying goes) I decided to try
Trond's code. And you know what? Not only did it NEVER miss the "n" key
when I pressed it, my own app STILL captured all of them too! So, what
do you have to say now?