cztomczak / cefpython

Python bindings for the Chromium Embedded Framework (CEF)
Other
3.09k stars 473 forks source link

cef.DpiAware.EnableHighDpiSupport() doesn't work well. Update pyinstaller example. Update docs. #530

Open amstaff8 opened 5 years ago

amstaff8 commented 5 years ago

Hi

I can't get cef python to work with dpi adware. I have cef python 66 with pywin32 224 (python 3.6.8).

I added the method cef.DpiAware.EnableHighDpiSupport() after line 28 of your example cefpython/examples/pywin32.py

But it doesn’t work (see the screenshot). I’ve tested it also with PyQt but the result is the same.

21_06_2019__12_12_02 21_06_2019__12_13_06

cztomczak commented 5 years ago

The wxpython.py example supports High DPI by default. Does it work for you?

Please provide exact steps to reproduce your issue from the screenshot.

amstaff8 commented 5 years ago

Same result. My steps:

  1. I’ve downloaded cefpython/examples/wxpython.py
  2. I ran pip install wxpython (and it has installed wxpython-4.0.6)
  3. I ran the command: python wxpython.py

This is the output:

[wxpython.py] System DPI settings: (192, 192) [wxpython.py] wx.GetDisplayPPI = (267, 267) [wxpython.py] wx.GetDisplaySize = (2736, 1824) [wxpython.py] MainFrame declared size: (900, 640) [wxpython.py] MainFrame DPI scaled size: (1800, 1280) [wxpython.py] MainFrame actual size: (1800, 1280)

And this is the result:

24_06_2019__16_38_34

This is my resolution:

24_06_2019__16_48_47

cztomczak commented 5 years ago

This is DPI awareness issue. The example sets DPI awareness via a call to cef.DpiAware.EnableHighDpiSupport, but the call occurs too late in Python programs on some machines, it's a race condition. DPI awareness needs to be set for the executable before Python program executes. Same for the subprocess.exe executable that runs the Renderer subprocess. If you remove the call to EnableHighDpiSupport you will get rid of double rendering, but it will result in a different rendering issue with app appearing blurry.

It is recommended to set High DPI awareness through manifest that is attached inside or next to executable. Such manifest must be added for both main executable and subprocess executable. Sometimes it is too late to set DPI awareness during runtime, so it must be set through manifest. If you want to fix the issue on a developer machine then go find python.exe executable and set appropriate file properties. On Windows 7 select "Compatibility" tab and check "Disable display scaling on high DPI settings".

For example to set DPI awareness through manifest create two files: myapp.exe.manifest and subprocess.exe.manifest in the same directory where myapp.exe and subprocess.exe executables reside. Edit both of these manifest files, so that they contain these contents:

<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <application>
      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"></supportedOS> 
      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"></supportedOS> 
      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"></supportedOS> 
      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"></supportedOS> 
    </application>
  </compatibility>

  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
    <security>
      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
        <requestedExecutionLevel level="asInvoker"></requestedExecutionLevel>
      </requestedPrivileges>
    </security>
  </trustInfo>

  <asmv3:application>
    <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
      <dpiAware>true/PM</dpiAware>
    </asmv3:windowsSettings>
  </asmv3:application>

</asmv1:assembly>

Some users report that this still doesn't work for them and it works only after embedding manifest inside the exe with such command:

mt.exe -nologo -manifest myapp.exe.manifest -outputresource:"myapp.exe;1"
mt.exe -nologo -manifest subprocess.exe.manifest -outputresource:"subprocess.exe;1"

When using pyinstaller you might not be able to embed manifest inside myapp.exe executable, in my case it resulted in errors. In such case you have to modify the existing manifest file that is placed next to exe, it was generated by pyinstaller. You cannot overwrite it entirely with a new file, because it contains other necessary options, so manual editing is required. Embedding manifest inside subprocess.exe executable will alway work fine.

pi-slh commented 5 years ago

I've recently spent some time working through a similar issue in our cefpython-based app.

The app is intended to run on a Dell tablet running Windows 10 where the system Display Scale setting is very likely to be greater than 100%. It is using CEF 66, python 3.7.3 and wxPython 4.0.6.

The app was exhibiting the rendering artefact described and illustrated in this issue whereby the UI is drawn twice - once at the desired scale, and a duplicated, smaller version aligned in the top left of the screen. Touch and mouse events would go to the right place in the full-sized UI, but the updates would only be rendered in the smaller version.

Setting a chrome switch to disable GPU compositing has 'fixed' this issue.

Using any of these switches 'fixes' the issue.

--disable-gpu
--disable-gpu-compositing
--disable-gpu-driver-bug-workarounds

The solution in my situation is;

cef.Initialize(settings={}, switches={'disable-gpu-compositing': None})

I've seen this issue on Dell machines with integrated Intel GPUs and with an Nvidia card.

I haven't identified a root cause.

cztomczak commented 5 years ago

@pi-slh Have you embedded a DPI aware manifest in your app? See my comment: https://github.com/cztomczak/cefpython/issues/530#issuecomment-505066492

pi-slh commented 5 years ago

@cztomczak I saw the comment and tried putting a .manifest next to both the python.exe and the subprocess.exe that are the entry points for our app. I'm not sure that they are doing anything - my reading suggests the python.exe has an embedded manifest and therefore ignores the .manifest next to it. We're not building our own executable at the moment, just running python.exe scriptname.py to startup. I've been unable to get the layout I want in the browser by setting the High DPI scaling override on the application executable, so I haven't looked into manifests any further, assuming that it wouldn't give me the result I was after. Thanks for your attention :)

cztomczak commented 5 years ago

@pi-slh The issue is with DPI support and fixing it by disabling GPU is not a good idea. Unless you have a different issue. What do you mean by "I've been unable to get the layout I want in the browser by setting the High DPI scaling override on the application executable"? Are you sure you've checked the right option? Have you tested with original wxpython.py example?

dfb commented 5 years ago

For anyone stuck on this, it never worked for me using a manifest file next to the exe, but it did work if I embedded the manifest inside the exe:

mt.exe -nologo -manifest subprocess.exe.manifest -outputresource:"subprocess.exe;1"

cztomczak commented 4 years ago

We should update the pyinstaller example to embed the necessary High DPI manifest files in the exe's. Marking this for next release.

marcelomanzo commented 4 years ago

@cztomczak I think I found an easier solution... check this out...

import ctypes

Set DPI Awareness (Windows 10 and 8)

errorCode = ctypes.windll.shcore.SetProcessDpiAwareness(0)

Set DPI Awareness (Windows 7 and Vista)

success = ctypes.windll.user32.SetProcessDPIAware()

More info here: https://stackoverflow.com/questions/44398075/can-dpi-scaling-be-enabled-disabled-programmatically-on-a-per-session-basis

cztomczak commented 4 years ago

It might fix the issue, but it's still not guaranteed.

dfb commented 4 years ago

It might fix the issue, but it's still not guaranteed.

FWIW we tried this fix but it didn't work reliably for us - so far only the embedded manifest file approach has worked.

bekatd commented 4 years ago

It might fix the issue, but it's still not guaranteed.

FWIW we tried this fix but it didn't work reliably for us - so far only the embedded manifest file approach has worked.

It didn't work reliably or didn't work at all for you? Since I am not having any issues with setting dpi scale programmatically

dfb commented 4 years ago

It didn't work reliably or didn't work at all for you? Since I am not having any issues with setting dpi scale programmatically

IIRC it seemed to work on some machines but not others, or it seemed to work sometimes but not always (which is consistent with the problem being a race condition). But with the manifest file embedded, it has worked reliably.

bnbchu commented 3 years ago

Tried embedding the manifest on subprocess.exe. However, the browser stopped loading, giving the below error

[1005/141244.841:ERROR:browser_gpu_channel_host_factory.cc(119)] Failed to launch GPU process. [1005/141244.859:ERROR:gpu_process_transport_factory.cc(1017)] Lost UI shared context.

cztomczak commented 3 years ago

@bnbchu What does the manifest look like?

bnbchu commented 3 years ago

@bnbchu What does the manifest look like?

@cztomczak thanks for the follow-up

I couldn't find a manifest file for subprocess.exe so created one based on the guide above.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>  
  <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
      <application xmlns="urn:schemas-microsoft-com:asm.v3">
          <windowsSettings>
          <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
          <dpiAware>true</dpiAware>
      </windowsSettings>
  </application>
</assembly>

For my main exe, I edited the manifest file generated by pyinstaller.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  <assemblyIdentity name="JD Tool" processorArchitecture="amd64" type="win32" version="1.0.0.0"/>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
      </requestedPrivileges>
    </security>
  </trustInfo>
  <dependency>
    <dependentAssembly>
      <assemblyIdentity language="*" name="Microsoft.Windows.Common-Controls" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" type="win32" version="6.0.0.0"/>
      <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"/>
    </dependentAssembly>
  </dependency>
  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <application>
      <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
    </application>
  </compatibility>
  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
    </windowsSettings>
  </application>
</assembly>
cztomczak commented 3 years ago

@bnbchu Try embedding this manifest in subprocess.exe:

<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <application>
      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"></supportedOS> 
      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"></supportedOS> 
      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"></supportedOS> 
      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"></supportedOS> 
    </application>
  </compatibility>

  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
    <security>
      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
        <requestedExecutionLevel level="asInvoker"></requestedExecutionLevel>
      </requestedPrivileges>
    </security>
  </trustInfo>

  <asmv3:application>
    <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
      <dpiAware>true/PM</dpiAware>
    </asmv3:windowsSettings>
  </asmv3:application>

</asmv1:assembly>
bnbchu commented 3 years ago

@bnbchu Try embedding this manifest in subprocess.exe:

<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <application>
      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"></supportedOS> 
      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"></supportedOS> 
      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"></supportedOS> 
      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"></supportedOS> 
    </application>
  </compatibility>

  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
    <security>
      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
        <requestedExecutionLevel level="asInvoker"></requestedExecutionLevel>
      </requestedPrivileges>
    </security>
  </trustInfo>

  <asmv3:application>
    <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
      <dpiAware>true/PM</dpiAware>
    </asmv3:windowsSettings>
  </asmv3:application>

</asmv1:assembly>

@cztomczak That worked! Appreciate the support and much thanks!

allsyntax commented 2 years ago

Can someone show how to include the manifest data with just a basic cefpython script? I don't have any exe's. Just running a script that uses cefpython that launches an HTML file in the cef browser. Calling: cef.DpiAware.EnableHighDpiSupport() does not work.