I have a particular problem with handling a download link within Chromium.
The issue is not so much the download (answered here quite well: CEF4 Chromium Delphi 10.2 Tokyo - how to handle download dialogue? )
...but this particular link is tagged with target="_blank".
Which triggers OnBeforePopup with targetDisposition=WOD_NEW_FOREGROUND_TAB
However, in almost all the example code, the OnBeforePopup handler has the following code:
// For simplicity, this demo blocks all popup windows and new tabs
Result := (targetDisposition in [WOD_NEW_FOREGROUND_TAB, WOD_NEW_BACKGROUND_TAB, WOD_NEW_POPUP, WOD_NEW_WINDOW]);
This effectively blocks that link from proceeding, thus the OnBeforeDownload event never triggers.
If I comment out the popup blocker, the default behaviour seems to be to open a new blank window, and then proceed with the download events as expected.
However, the download never quite completes (it goes to 100% but never "complete"), and the new window never goes away.
My question is in two parts:
Could I get some guidance on how to either create a new browser window that I have control over in the OnBeforePopup event?
How to I make the download properly complete?
Note: If I paste the actual targetURL of the download file into the address bar, the download completes quite cheerfully, so I suspect the key is in the handling of the default window.
Note: I have found the CEF API documentation, it is not super informative.
Note: I'm aware TabBrowser2 handles the popup intercept, but it is far from clear what is going on, apparently calling a client window to then call the main window which in turn calls the client window again. Plus the structure I have so far does not really lend itself to that solution.
The
Partial answer: The PopupBrowser demo shows it much more clearly. and at least
partially documents what is happening.
From the comments:
// VCL components *MUST* be created and destroyed in the main thread but CEF executes the
// TChromium.OnBeforePopup in a different thread.
// For this reason this demo creates a hidden popup form (TChildForm) in case CEF needs to show a popup window.
// TChromium.OnBeforePopup calls TChildForm.CreateClientHandler to initialize some parameters and create the new ICefClient.
// After that, it sends a CEF_CREATENEXTCHILD message to show the popup form and create a new one.
This explains what is happening fairly clearly.
CreateClientHandler(var aClient : ICefClient...
populates the clienthandler parameter passed in the BeforePopup call.
You can open the new tab using the event ChromiumBeforePopup
procedure ChromiumBeforePopup(Sender: TObject; const browser: ICefBrowser;
const frame: ICefFrame; const targetUrl, targetFrameName: ustring;
targetDisposition: TCefWindowOpenDisposition; userGesture: Boolean;
const popupFeatures: TCefPopupFeatures; var windowInfo: TCefWindowInfo; var client: ICefClient;
var settings: TCefBrowserSettings; var extra_info: ICefDictionaryValue; var noJavascriptAccess,
Result: Boolean);
begin
Result := not(CreateClientHandler(windowInfo, client, targetFrameName, popupFeatures));
end;
Related
I'm making a very simple Delphi console application ({$APPTYPE CONSOLE}) with a single TChromiumWindow on the main form. The purpose of the application is to retrieve a webpage, process the HTML and output some JSON to the console. This can not be done using plain HTTP requests due to the nature of the webpage, which requires running some javascript as well.
Everything works as expected, except for one problem. The chromium components output some error messages to the console as well, which makes my JSON invalid! For example, I always get the following two error messages on startup:
[0529/133941.811:ERROR:gpu_process_transport_factory.cc(990)] Lost UI shared context.
[0529/133941.832:ERROR:url_request_context_getter_impl.cc(130)] Cannot use V8 Proxy resolver in single process mode.
Of course the best solution would be to not get any error messages in the first place, but for several reasons (which mostly have to do with company legacy code) I can't for example disable single process mode.
So the next best thing would be to keep these error messages from being printed to the console. I've tried setting
GlobalCEFApp.LogSeverity := LOGSEVERITY_DISABLE;
but that didn't help. Specifying a logfile using GlobalCEFApp.LogFile doesn't help either.
So how can I prevent the Chromium components from writing to the console at all?
The TChromium component provides an OnConsoleMessage event with signature :
TOnConsoleMessage = procedure(Sender: TObject; const browser: ICefBrowser;
const message, source: ustring; line: Integer;
out Result: Boolean) of object;
If you handle this event and set the Result variable to true the message output to the console is suppressed.
Set LogSeverity to LogSeverity.Fatal or n other desired.
var settings = new CefSettings()
{
//By default CefSharp will use an in-memory cache, you need to specify a Cache Folder to persist data
CachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefSharp\\Cache"),
//Set log severity to showup only fatal errors.
LogSeverity = LogSeverity.Fatal,
};
//Autoshutdown when closing
CefSharpSettings.ShutdownOnExit = true;
//Perform dependency check to make sure all relevant resources are in our output directory.
Cef.Initialize(settings, performDependencyCheck: true, browserProcessHandler: null);
After launching osk.exe with ShellExecuteEx() I would like to position the keyboard window relative to the data-entry fields, so that it doesn't cover them.
How do I set the window position for the osk before calling it?
Also, how can I have the application hide the osk when I am finished?
You can use FindWindow using the window class "OSKMainClass" to get the window handle, and then SetWindowPos on that handle to position it to the coordinates you want. (You may need to use the control's ClientToScreen method to convert to the proper coordinates, but I'll let you figure that part out.)
// Off the top of my head - not at a machine that has a Delphi compiler at
// the moment.
var
OSKWnd: HWnd;
begin
OSKWnd := FindWindow(PChar('OSKMainClass'), nil);
if OSKWnd <> 0 then
begin
SetWindowPos(OSKWnd,
HWND_BOTTOM,
NewPos.Left,
NewPos.Top,
NewPos.Width,
NewPos.Height,
0);
end;
end;
Code taken in part from a CodeProject article related to the same topic. I got the window class using AutoHotKey's Window Spy utility.
Notes:
Remy Lebeau points out in a comment that you should make sure to use CreateProcess() or ShellExecuteEx() so that you get back a process handle that can then be passed to WaitForInputIdle() before calling FindWindow(). Otherwise the call to FindWindow() may happen before OSK creates the window.
mghie points out in a comment that the only way he could get this to work was by running the app as Administrator; otherwise the call to SetWindowPos() resulted in an "Access Denied (5)".
I can,t move the window as mentioned above.
Using Win32 commands, SetWindow or MoveWindow not worked for on screen keyboard.
Its worked only while running exe in admin privilege.
I think its not a good solution.
I found another solution.
Please go through this.
After trying using registry values its worked well i can move on screen keyboard in my application
try
{
RegistryKey myKey = Registry.CurrentUser.OpenSubKey(#"SOFTWARE\Microsoft\Osk", true);
myKey.SetValue("WindowLeft", oskLeft, RegistryValueKind.DWord);
myKey.SetValue("WindowTop", oskTop, RegistryValueKind.DWord);
}
catch
{
//Log the error
}
I'm wondering if someone has an example on how can be used the TJvProgramVersionCheck component performing the check via HTTP.
The example in the JVCL examples dir doesn't show how to use HTTP
thank you
The demo included in your $(JVCL)\Examples\JvProgramVersionCheck folder seems to be able to do so. Edit the properties of the JVProgramVersionHTTPLocation, and add the URL to it's VersionInfoLocation list (a TStrings). You can also set up any username, password, proxy, and port settings if needed.
You also need to add an OnLoadFileFromRemote event handler. I don't see anything in the demo that addresses that requirement, but the source code says:
{ Simple HTTP location class with no http logic.
The logic must be implemented manually in the OnLoadFileFromRemote event }
It appears from the parameters that event receives that you do your checking there:
function TJvProgramVersionFTPLocation.LoadFileFromRemoteInt(
const ARemotePath, ARemoteFileName, ALocalPath, ALocalFileName: string;
ABaseThread: TJvBaseThread): string;
So you'll need to add an event handler for this event, and then change the TJVProgramVersionCheck.LocationType property to pvltHTTP and run the demo. After testing, it seems you're provided the server and filename for the remote version, and a local path and temp filename for the file you download. The event handler's Result should be the full path and filename of the newly downloaded file. Your event handler should take care of the actual retrieval of the file.
There are a couple of additional types defined in JvProgramVersionCheck.pas, (TJvProgramVersionHTTPLocationICS and TJvProgramVersionHTTPLocationIndy, both protected by compiler defines so they don't exist in the default compilation. However, setting the ICS related define resulted in lots of compilation errors (it apparently was written against an old version of ICS), and setting the Indy define (and then setting it again to use Indy10 instead) allowed it to compile but didn't change any of the behavior. I'm going to look more into this later today.
Also, make sure that the VersionInfoLocation entry is only the URL (without the filename); the filename itself goes in the VersionInfoFileName property. If you put it in the URL, it gets repeated (as in http://localhost/Remote/ProjectVersions_http.iniProjectVersions_http.ini, and will fail anyway. (I found this while tracing through the debugger trying to solve the issue.)
Finally...
The solution is slightly (but not drastically) complicated. Here's what I did:
Copy JvProgramVersionCheck.pas to the demo folder. (It needs to be recompiled because of the next step.)
Go to Project->Options->Directories and Conditionals, and add the following line to the DEFINES entry:
USE_3RDPARTY_INDY10;USE_THIRDPARTY_INDY;
Delete the JvProgramVersionHTTPLocation component from the demo form.
Add a new private section to the form declaration:
private
HTTPLocation: TJvProgramVersionHTTPLocationIndy;
In the FormCreate event, add the following code:
procedure TForm1.FormCreate(Sender: TObject);
const
RemoteFileURL = 'http://localhost/';
RemoteFileName = 'ProjectVersions_http.ini';
begin
HTTPLocation := TJvProgramVersionHTTPLocationIndy.Create(Self); // Self means we don't free
HTTPLocation.VersionInfoLocationPathList.Add(RemoteFileURL);
HTTPLocation.VersionInfoFileName := RemoteFileName;
ProgramVersionCheck.LocationHTTP := HTTPLocation;
ProgramVersionCheck.LocationType := pvltHTTP;
VersionCheck; // This line is already there
end;
In the ProgramVersionCheck component properties, expand the VersionInfoFileOptions property, and change the FileFormat from hffXML to hffIni.
Delete or rename the versioninfolocal.ini from the demo's folder. (If you've run the app once, it stores the http location info, and the changes above are overwritten. This took a while to track down.)
Make sure your local http server is running, and the ProjectVersions_http.ini file is in the web root folder. You should then be able to run the demo. Once the form appears, click on the Edit History button to see the information retrieved from the remote version info file. You'll also have a new copy of the versioninfolocal.ini that has the saved configuration info you entered above.
Using Delphi 7 I am reading from a serial port.
The read is always preceded by a write which triggers the h/w to measure from a sensor and write something for me to read (and there is always something to read).
I have two possibilities: manually enter a command and click a button to write that to the serial port (read model or f/w version, etc) or click a button to loop reading measurements until a stop button is pressed. These both use the same internal functions, so the code looks something like this:
WriteSerial('?model');
SerialData := ReadSerial(); // returns string
WriteSerial('?fw');
SerialData := ReadSerial();
and
while stopButtonNotPressed do
begin
WriteSerial('?data');
SerialData := ReadSerial();
Memo1.Lines.Add(SerialData );
end;
The first variant (manually entering a command & pressing a button) is always successful, no matter how quickly or slowly I enter commands (hold down button for repeat), where are the second goes
pass
fail
pass
pass
fail
pass
pass
fail
... add infinitum
adding calls to sleep produces nothing, but trying to debug, I found that if I add a modal dialog box MsgDialog, 'Please close this dialog...', mtInfo, [mrOK]); to the loop, then it no longer fails.
Now, it doesn't look like timing (else surely adding Sleep(2000); to the loop would make it pass & does not, so why does pressing a button on the main form or the modal dialog cause it to succeed?
Btw, the h/w user guide says nothing of CTS / RTS, and the sole code example provide also does not.
Note: if I manually enter ?data repeatedly it never fails ...
Any ideas?
Your serial devices need time to react, so obviously you need a break for the device to catch up. When you use the keyboard to push the button you're providing the brake it needs because the keyboard repeat isn't all that fast.
As you say Sleep(2000) should provide plenty of "break", but there are two other potential problems you'll need to take care of:
Serial communication isn't necessary buffered: Sleep(2000) might be too long!
The serial library you're using might be using windows messages to process incoming bytes. Sleep() inhibits the message pump, so no more messages flow towords your application
Try "sleeping" using something like this:
procedure BusyWait(ms: Cardinal);
var StopAt: TDateTime;
begin
StopAt := Now + EncodeTime(0, 0, ms div 1000, ms mod 1000);
while StopAt > Now do
begin
Application.ProcessMessages;
Sleep(50); // per MichaĆ Niklas's suggestion, to keep the CPU from reaching 100%
end;
end;
This routine will wait, but it'll keep the message pump going, allowing your serial library to receive messages. If that's the problem...
Maybe adding Application.ProcessMessages() before Sleep() will help.
As far as I've been able to find out, Windows doesn't offer an API function to tell what application has registered a global hotkey (via RegisterHotkey). I can only find out that a hotkey is registered if RegisterHotkey returns false, but not who "owns" the hotkey.
In the absence of a direct API, could there be a roundabout way? Windows maintains the handle associated with each registred hotkey - it's a little maddening that there should be no way of getting at this information.
Example of something that likely wouldn't work: send (simulate) a registered hotkey, then intercept the hotkey message Windows will send to the process that registered it. First, I don't think intercepting the message would reveal the destination window handle. Second, even if it were possible, it would be a bad thing to do, since sending hotkeys would trigger all sorts of potentially unwanted activity from various programs.
It's nothing critical, but I've seen frequent requests for such functionality, and have myself been a victim of applications that register hotkeys without even disclosing it anywhere in the UI or docs.
(Working in Delphi, and no more than an apprentice at WinAPI, please be kind.)
One possible way is to use the Visual Studio tool Spy++.
Give this a try:
Run the tool (for me, it's at C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\spyxx_amd64.exe or you can download it). Note: there is spyxx.exe (32-bit version) and spyxx_amd64.exe (64-bit version) - if you don't see anything in 64-bit use the 32-bit version (ie.catches messages only in same architecture)
In the menu bar, select Spy -> Log messages... (or hit Ctrl + M)
Check All Windows in System in the Additional Windows frame
Switch to the Messages tab
Click the Clear All button
Select WM_HOTKEY in the listbox, or check Keyboard in Message Groups (if you're OK with more potential noise)
Click the OK button
Press the hotkey in question (Win + R, for example)
Select the WM_HOTKEY line in the Messages (All Windows) window, right click, and select Properties... in the context menu
In the Message Properties dialog, click the Window Handle link (this will be the handle for the window that received the message)
Click the Synchronize button on the Window Properties dialog. This will show the window in the main Spy++ window treeview (if it's windows itself or some popup application it shows nothing).
On the Window Properties dialog, select the Process tab
Click the Process ID link. This will show you the process (In my Win + R case: EXPLORER)
Your question piqued my interest, so I've done a bit of digging and while, unfortunately I don't have a proper answer for you, I thought I'd share what I have.
I found this example of creating keyboard hook (in Delphi) written in 1998, but is compilable in Delphi 2007 with a couple of tweaks.
It's a DLL with a call to SetWindowsHookEx that passes through a callback function, which can then intercept key strokes: In this case, it's tinkering with them for fun, changing left cursor to right, etc. A simple app then calls the DLL and reports back its results based on a TTimer event. If you're interested I can post the Delphi 2007 based code.
It's well documented and commented and you potentially could use it as a basis of working out where a key press is going. If you could get the handle of the application that sent the key strokes, you could track it back that way. With that handle you'd be able to get the information you need quite easily.
Other apps have tried determining hotkeys by going through their Shortcuts since they can contain a Shortcut key, which is just another term for hotkey. However most applications don't tend to set this property so it might not return much. If you are interested in that route, Delphi has access to IShellLink COM interface which you could use to load a shortcut up from and get its hotkey:
uses ShlObj, ComObj, ShellAPI, ActiveX, CommCtrl;
procedure GetShellLinkHotKey;
var
LinkFile : WideString;
SL: IShellLink;
PF: IPersistFile;
HotKey : Word;
HotKeyMod: Byte;
HotKeyText : string;
begin
LinkFile := 'C:\Temp\Temp.lnk';
OleCheck(CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER, IShellLink, SL));
// The IShellLink implementer must also support the IPersistFile
// interface. Get an interface pointer to it.
PF := SL as IPersistFile;
// Load file into IPersistFile object
OleCheck(PF.Load(PWideChar(LinkFile), STGM_READ));
// Resolve the link by calling the Resolve interface function.
OleCheck(SL.Resolve(0, SLR_ANY_MATCH or SLR_NO_UI));
// Get hotkey info
OleCheck(SL.GetHotKey(HotKey));
// Extract the HotKey and Modifier properties.
HotKeyText := '';
HotKeyMod := Hi(HotKey);
if (HotKeyMod and HOTKEYF_ALT) = HOTKEYF_ALT then
HotKeyText := 'ALT+';
if (HotKeyMod and HOTKEYF_CONTROL) = HOTKEYF_CONTROL then
HotKeyText := HotKeyText + 'CTRL+';
if (HotKeyMod and HOTKEYF_SHIFT) = HOTKEYF_SHIFT then
HotKeyText := HotKeyText + 'SHIFT+';
if (HotKeyMod and HOTKEYF_EXT) = HOTKEYF_EXT then
HotKeyText := HotKeyText + 'Extended+';
HotKeyText := HotKeyText + Char(Lo(HotKey));
if (HotKeyText = '') or (HotKeyText = #0) then
HotKeyText := 'None';
ShowMessage('Shortcut Key - ' + HotKeyText);
end;
If you've got access to Safari Books Online, there is a good section about working with shortcuts / shell links in the Borland Delphi 6 Developer's Guide by Steve Teixeira and Xavier Pacheco. My example above is a butchered version from there and this site.
Hope that helps!
After some research, it appears that you'd need to get access to the internal structure that MS uses to store the hotkeys. ReactOS has a clean room implementation that implements the GetHotKey call by iterating an internal list and extracting the hotkey that matches the parameters to the call.
Depending on how close ReactOS' implementation is to the MS implementation, you may be able to poke around in memory to find the structure, but that's over my head...
BOOL FASTCALL
GetHotKey (UINT fsModifiers,
UINT vk,
struct _ETHREAD **Thread,
HWND *hWnd,
int *id)
{
PHOT_KEY_ITEM HotKeyItem;
LIST_FOR_EACH(HotKeyItem, &gHotkeyList, HOT_KEY_ITEM, ListEntry)
{
if (HotKeyItem->fsModifiers == fsModifiers &&
HotKeyItem->vk == vk)
{
if (Thread != NULL)
*Thread = HotKeyItem->Thread;
if (hWnd != NULL)
*hWnd = HotKeyItem->hWnd;
if (id != NULL)
*id = HotKeyItem->id;
return TRUE;
}
}
return FALSE;
}
I presume this thread on sysinternals was asked by someone related to this question, but I thought I'd link to it anyway to keep the two together. The thread looks very intriguing, but I suspect that some deep dive spelunking would need to happen to figure this out without access to the MS internals.
Off the top of my head, you might try enumerating all windows with EnumWindows, then in the callback, send WM_GETHOTKEY to each window.
Edit: Apparrently I was wrong about that. MSDN has more information:
WM_HOTKEY is unrelated to the WM_GETHOTKEY and WM_SETHOTKEY hot keys. The WM_HOTKEY message is sent for generic hot keys while the WM_SETHOTKEY and WM_GETHOTKEY messages relate to window activation hot keys.
Note: Here is a program purporting to have the functionality you are looking for. You could try decompiling it.
Another thread mentions a global NT level keyboard hook:
Re-assign/override hotkey (Win + L) to lock windows
maybe you can get the handle of the process that called the hook that way, which you can then resolve to the process name
(disclaimer: I had it in my bookmarks, haven't really tried/tested)
I know you can intercept the stream of messages in any window within your own process - what we used to call subclassing in VB6. (Though I do not remember the function, perhaps SetWindowLong?) I am unsure if you can do this for windows outside your own process. But for the sake of this post lets assume you find a way to do that. Then you can simply intercept the messages for all top level windows, monitor for the WM_HOTKEY message. You wouldn't be able to know all the keys right off the bat, but as they were pressed you could easily figure out what application was using them. If you persisted your results to disk and reloaded each time your monitor application was run you could increase the performance of your application over time.
This doesn't exactly answer the part of the question that is about the Windows API, but it answers the part of the question that is about a list of global hotkeys and the applications that "own" them.
The free Hotkey Explorer at http://hkcmdr.anymania.com/ shows a list of all global hotkeys and the applications that own them. This just has helped me figure out why an application-specific shortcut key stopped working and how to fix it (by reconfiguring the registered global hotkey in the app that had it registered), within a few seconds.