Thursday, May 15, 2008

SnTT: Disable the Print Screen Key (Win32 API)

We've all heard that you can't disable the Print Screen key in Windows, which means $KeepPrivate and setting the form properties to disable printing were of limited use. It turns out that Windows has had a way to do it since Windows 95, and it's still present in Windows XP and Vista: RegisterHotKey.

A Limitation That Works To Our Benefit


According to the MSDN documentation, "the RegisterHotKey function defines a system-wide hot key." Furthermore "the system posts the WM_HOTKEY message to the message queue of the window with which the hot key is associated." So what does that mean to you?

Whenever anything happens in Windows, messages are generated. Applications respond to these messages either by handling them or surfacing them as events for developers. That's how Notes uses Ctrl+A to select all the documents in a view, but when a user double-clicks on a document you have an event you can program. It's all about the messages, and who handles them.

In some other development environments you can create your own message handler, or hook. Notes doesn't allow you do that kind of low-level integration, and while that's frustrating for me in this case that works to our benefit! All you have to do is reset the Print Screen key so it gets directed to Notes. Notes isn't programmed to be aware of the message so it gets ignored. The end result is the Print Screen gets pressed and absolutely nothing happens.

The Code


(Declarations)

Declare Function FindWindowByClass Lib "user32" Alias "FindWindowA" (Byval lpClassName As String, Byval lpWindowName As Long) As Long
Declare Function GetTickCount Lib "kernel32" () As Long
Declare Function RegisterHotKey Lib "user32" (Byval hWnd As Long, Byval id As Long, Byval fsModifiers As Long, Byval vk As Long) As Long
Declare Function UnregisterHotKey Lib "user32" (Byval hWnd As Long, Byval id As Long) As Long
Declare Function GlobalAddAtom Lib "kernel32" Alias "GlobalAddAtomA" (Byval lpString As String) As Long
Declare Function GlobalDeleteAtom Lib "kernel32" (Byval nAtom As Long) As Long
Declare Function GetActiveWindow Lib "user32" Alias "GetActiveWindow" () As Long

Const VK_SNAPSHOT = &H2C

Const MOD_ALT = &H1
Const MOD_CONTROL = &H2
Const MOD_SHIFT = &H4

Dim g_hWnd As Long
Dim g_Print As Long
Dim g_AltPrint As Long
Dim g_CtrlPrint As Long
Dim g_ShiftPrint As Long

Sub Queryopen(Source As Notesuidocument, Mode As Integer, Isnewdoc As Variant, Continue As Variant)

'Get a handle to the Notes client window so you can tell Windows which window to hook the hotkeys to
g_hWnd = FindWindowByClass("Notes", 0)

'Register new identifiers for our custom hotkeys. GetTickCount returns a number that's based on the system clock,
' so you know it won't be duplicated.
g_Print = GlobalAddAtom(Cstr(GetTickCount))
g_AltPrint = GlobalAddAtom(Cstr(g_Print) + Cstr(GetTickCount))
g_CtrlPrint = GlobalAddAtom(Cstr(g_AltPrint) + Cstr(GetTickCount))
g_ShiftPrint = GlobalAddAtom(Cstr(g_CtrlPrint) + Cstr(GetTickCount))

'Now register the hotkeys
Call RegisterHotKey(g_hWnd, g_Print, 0&, VK_SNAPSHOT) 'PrintScreen
Call RegisterHotKey(g_hWnd, g_AltPrint, MOD_ALT, VK_SNAPSHOT) 'Alt+PrintScreen
Call RegisterHotKey(g_hWnd, g_CtrlPrint, MOD_CONTROL, VK_SNAPSHOT) 'Ctrl+PrintScreen
Call RegisterHotKey(g_hWnd, g_ShiftPrint, MOD_SHIFT, VK_SNAPSHOT) 'Shift+PrintScreen
End Sub

Sub Queryclose(Source As Notesuidocument, Continue As Variant)

'Unregister the hotkeys
Call UnregisterHotKey(g_hWnd, g_Print)
Call UnregisterHotKey(g_hWnd, g_AltPrint)
Call UnregisterHotKey(g_hWnd, g_CtrlPrint)
Call UnregisterHotKey(g_hWnd, g_ShiftPrint)

'Delete our custom entries
Call GlobalDeleteAtom(g_Print)
Call GlobalDeleteAtom(g_AltPrint)
Call GlobalDeleteAtom(g_CtrlPrint)
Call GlobalDeleteAtom(g_ShiftPrint)
End Sub
This LotusScript was converted to HTML using the ls2html routine,
provided by Julian Robichaux at nsftools.com.

The Downside


Come on... you knew it couldn't be all good. :-) While this code in active Print Screen is disabled SYSTEM WIDE. Here I have it enabled in a form's QueryOpen, but you could do some tests, say for $KeepPrivate or check user roles, to enable it selectively. (Yes, I know end users can remove $KeepPrivate.) If I put this into production I would be nice and actually tell them that Print Screen is disabled, though. I'll leave that as an exercise for the reader.

Sources: Digital Inspiration blog
Answers.Com RegisterHotKey sample
vbAccelerator clsRegHotKey

[Update 5/16/08 - Removed two unused constants]

9 comments:

  1. huh. that's pretty cool. doesn't defeat my camera though. ;-)

    ReplyDelete
  2. No, it doesn't, and there are a few other techniques someone could use to clip the screen. But it does finally lay to rest the fallacy that you can't disable Print Screen. :-p

    ReplyDelete
  3. This is a wondeful tip. Though not a fool prof way. But it works.

    One limitation that I was able to find out that this code DOES NOT work when I preview the form from Domino Designer. If you why then do reply at
    naveen472-472@yahoo.co.in

    Thnx
    Naveen

    ReplyDelete
  4. It will work if you preview from Domino Designer preview if you put the code in the PostOpen of the form.

    ReplyDelete
  5. I know this was i while back, but i fund it just now after searching for almost a full day.

    Google is flooded with infromation that has got nothing to do with my searchterms, this post on the pther hand is spot on and full of information.

    Thanks a million!

    /Linus

    ReplyDelete
  6. You're welcome. Glad you found it useful. :-)

    ReplyDelete
  7. thank you, thanku thankooo for such a wonderful tip :-) :-) ...

    ReplyDelete
  8. would u please tell me how to execute this code as I am a oracle devloper so not sure how to execute this code and make it work. Thanks in advance.

    ReplyDelete
  9. Where are you getting stuck? I included the full code along with the exact form events you need to use.

    ReplyDelete