OK folks... how would you solve this....

OK folks... how would you solve this....

I have a Delphi desktop app - it requires a user to be logged in to use it.  We now have a requirement that the app needs to lock the user out after an inactive period, at which point the user can't do anything without reentering credentials.

Here's the rub.  We need the main thread to continue doing stuff while the app is locked.  So I obviously can't show a modal dialog - that stops the main thread.  

Our first crack was to use DisableTaskWindows - that worked well, until the main thread pops up a modal dialog over the currently active form (the user lock screen).  At that point the app basically locks up.

So, to recap,  user session times out, a lock screen shows, we call DisableTaskWindows, which locks other background forms, but keeps the main thread running, the main thread pops up a modal dialog on top and we're locked up.  

Help!

//cc Nick Hodges Martin Binder 

Comments

  1. > "We need the main thread to continue doing stuff" - see if you can make it switch to doing stuff in a secondary thread. Main thread should stay responsive - could even use a modal dialog then.

    ReplyDelete
  2. Ondrej Kelle given the way the app is designed, I'm going to say that's not possible without a massive effort.

    ReplyDelete
  3. Ron Lawrence It may seem more difficult than it actually is. I had a similar situation with an installer application which was supposed to (kind of) lock the UI while the installation was in progress, and originally everything was running in the main thread. In my case, it wasn't too hard to rewrite it it to continue in a background thread. Just my 2c, of course - you know better what you're talking about.

    ReplyDelete
  4. Ondrej Kelle I understand what you're saying... let's just say this is a very feature rich app that I can't exactly redesign in a few weeks.

    ReplyDelete
  5. Does "doing stuff" include UI? I mean, do you need to process windows messages?

    ReplyDelete
  6. When the app goes "inactive", couldn't you just disable relevant controls and menu items (e.g. File, save, delete, etc)?  You could display the "App Locked" message in a non-modal box and/or some hidden label, banner, or the window title bar text.  It's hard to give specifics without seeing the user interface.

    ReplyDelete
  7. Ondrej Kelle Some of the stuff could involve the UI.  For example, one things I'm dealing with is this...

    The user kicks off some long process - we've got lots of this stuff in the app.  The process takes longer than the timeout period.  The screen locks, but we want this process (whatever it is running in the main thread) to continue.  

    One case we ran into is a long process that ends with a print dialog.  Once that print dialog pops up, bang... locked up.

    ReplyDelete
  8. Kevin Powick Sorry I'm being a bit vague, but it's a commercial app so I'm trying to avoid specifics.

    We can't really do what you're suggesting because we can't really anticipate what the active form will be when the app has to lock.  We've got a few thousand forms in the app, and any of them can be the active window when the app needs to lock.

    ReplyDelete
  9. My approach would be to use TMS Security Components to handle login and auto logout . Bruno Fierens , CEO of TMS ,may be able to provide more advice .

    ReplyDelete
  10. whenever I'm in a similar situation(not sure what to do), I just run a few tests and see what works best for the situation, maybe your application is doing a ton of stuff within main thread, I assume you got a db connection on a form or data module, but you can easily add another connection inside a thread and move a lot of logic within a TThread descending class, who knows, maybe it will prove to be very useful depending on what you guys need to get done in the near future.

    So, in a nutshell, I'd suggest spending an hour or two to see how difficult would it be to move the stuff from main thread to secondary thread(s), ideas will come up and you'll figure it out eventually.

    ReplyDelete
  11. Dorin Duminica , trust me when I say I can't move this stuff into another thread... without investing hundreds of hours.

    ReplyDelete
  12. Thousands of forms?  Wow.  Is the design such that the GUI has been separated from the processing (i.e. MVC)?  If so, you could "lock" the app by blocking updates in the controller, informing the user with a message until the app is "unlocked".  If the app is n-tier, where processing happens on the back-end, you could "lock" updates there.  Again, it's a little tough without application details, but I understand why you're being vague.

    ReplyDelete
  13. If you really can't move it, you might be able to centralize your "other stuff" in the OnIdle event of the application so they still run in the background but you can lock the UI.

    There's really no reason the main thread has to stop if you don't use ShowModal. Avoid that and you should be golden.

    Another thought, you might be able to use a StayOnTop window to log back in, disable the main menu (and any other navigation feature) and emulate a "lock" without blocking the main thread.

    ReplyDelete
  14. Kevin Powick Yeah, it's a huge app.  We don't have a consistent MVC type design were we can do anything at a controller level.  That's kind of why this has been so challenging to implement.  

    ReplyDelete
  15. Ron Lawrence then one very dirty solution would be to assign "proxy events"(that checks if user is logged in before invoking the actual event) or check if user is logged in when s/he tries to click a button or whatever...

    ReplyDelete
  16. Marshall Fryman that's why we went down the DisableTaskWindows route.  Your StayOnTop idea is interesting, but how would you disable whatever active form is under the StayOnTop form?  Keep in mind, the form(s) under the "lock" form could change while the main thread is doing stuff.  

    Take my report example... I kick off a report, and the active form is some status window... x minutes goes by and my lock screen comes up.  60 more minutes and my report is done and a modal print dialog pops up.  See my problem?

    ReplyDelete
  17. Dorin Duminica I can deal with a little dirt given my not so great other options.  How would you go about implementing that at a system wide level though?  I can see doing that if I had a few forms, but I've got a lot of code and forms to worry about.  So I'd need to intercept that stuff at a pretty low level that wouldn't require me to change every form.

    ReplyDelete
  18. Ron Lawrence in the OnCreate event of each form, basically, you can have a unit that is used by all form units with a global variable that is capable of doing something like:
    RegisterOnCreateProxy(AForm: TForm); <- what it does is:
    - stores AForm's OnCreate event and gives it a new OnCreate while storing it's class name in a record/class defined as:
    class|record TFormEntry
    FormClassName: string;
    InitialOnCreate: TEventDefinition
    CompListProxyEvents: TListOfTComponent;
    end;

    so when you're OnCreateProxy event is called, it will check AForm.ClassName in a list, finds it's OnCreate, "proxies" all events on "required visual components" to another list in CompListProxyEvents by their "Name".

    Not sure if it makes much sense... but it will work, however, I'd strongly recommend different alternatives, NO good will come out of this and it will also add a to memory usage...

    If you're interested in a demo, I might find some spare time and create a generic test app...

    ReplyDelete
  19. What if you lock the workstation, instead of the app itself?

    ReplyDelete
  20. Corrie Engelbrecht I wish we could, but I fear some installations are using shared domain logins.

    ReplyDelete
  21. Interesting concept on the proxy. I'm not sure how easy that would be to implement since it seems like you'd need to swap all CreateForm calls out.

    For the StayOnTop Security, you could adopt a base ancestor form (something like TAutoLogoutForm=class(TForm)) and then descend all of your other forms from it. This doesn't change the DFM, just the PAS (i.e., TSomeForm = class(TAutoLogoutForm) instead of TForm. I've actually done this and can tell you Delphi isn't impacted by this process.

    In the TAutoLogoutForm, override the CreateParam to force the form to be minimized if the security window is active. You might need to force the AutoLogoutForm to not restore / maximize using a similar technique in the AutoLogoutForm.

    When you pop the stayontop security form open, iterate through all the windows and minimize them. Also disable all menus and navigation. Now you can create windows as needed since they can't be "read", it's not modal so the main thread runs, when the user logs in again you iterate through the forms and do a restore and you have a very minor fix to your forms that can be done with a find/replace if you're careful

    ReplyDelete
  22. Marshall Fryman yeah, you also have to replace for example every "SysUtils" with "SysUtils, MySharedUnit", and in every OnCreate event, add the special call... as I said, it's very dirty and am against it, but if this is the only way... well, then be it.

    ReplyDelete
  23. Dorin Duminica So, in theory, what happens in the OnCreate? For instance, if TMyForm1 has this proxied OnCreate, what happens when it fires?

    Seems like you're suggesting:
    TMyForm1=class ...
       proc OnCreate...
    end

    proc TMyForm1.OnCreate...
    begin
    RegisterProxy(self.OnCreate);
    ...
    end;

    In that case, you'd actually have to create the form at least once before security kicked in to get the proxy registered.

    I've seen it coded where you do this kind of thing in the intialization but not in the OnCreate.

    Also, wouldn't you have a problem when you requested a CreateForm and the OnCreate didn't fire? If you blocked the creation of the form in your custom CreateForm, you'd then have a problem in the rest of the code since it presumes successful creation of the form.

    Just curious if you've successfully done this in the past. I find OnCreate to have a lot of problems because the instance isn't full resolved and visible at this point. Be interesting if there's more power there than I thought.

    ReplyDelete
  24. I would also use a stay on top window and intercept at the Application.OnMessage level: There check WM_KEY..., WM_MOUSE... etc and the window handle to see whether mouse / keyboard events are targeted at your log in window and discard all other  mouse / keyboard events that are not, so that they will never reach any other form / control.

    ReplyDelete
  25. Marshall Fryman errr, my bad, OnCreate event of the form calls a global function or a method on a global variable by passing itself as parameter..., at this point, before any initial code logic within the form occurs, every event on visual components can be proxy-ed... hope this makes more sense.
    I haven't done exactly this, but something similar in terms of user privileges...

    ReplyDelete
  26. Markus Joos I like this approach if you can let the underlying data remain visible. Usually an auto-logoff has to hide the data too but if it's not required, your approach is definitely simpler.

    ReplyDelete
  27. Dorin Duminica Ok, I think I see what you're aiming for. Very complicated but could conceivable allow field level control of visibility. Thanks for elaborating.

    ReplyDelete
  28. Marshall Fryman yeah, but again, it's a bad design, used a similar approach in the past, not proud of it at all, but sometimes the constraints lead us to these decisions ):

    ReplyDelete
  29. Dorin Duminica I've done field level control by creating descendants of the base edits (TEdit etc) and adding a property called FieldType that I could group the controls together with (address field, name field, etc). In the CreateParams of the field decendant, I checked the user security against the field type and forced the visibility to be invisible if the user didn't have the correct permissions. It was a real bear to get right but worked ok after the initial debugging. Biggest long-term issue was the coupling at the field level with my underlying security system.

    ReplyDelete
  30. On auto lock, for every visible form, inject a TPanel on top, covering the entire dialog with an onclick that pops the login dialog. A bit brute force perhaps...

    ReplyDelete
  31. Marshall Fryman I've done something similar, you can see a post like that on my blog(if you're interested), the thing is, if it's invisible, one can make it visible, the "Events" are the ones you want to prevent from happening...

    ReplyDelete
  32. One way is to lock whole workstation - http://msdn.microsoft.com/ru-ru/library/windows/desktop/aa376875(v=vs.85).aspx
    Another way is to hide all application forms and show the login form

    ReplyDelete

Post a Comment