microsoft / Windows.UI.Composition-Win32-Samples

Windows.UI.Composition Win32 Samples
MIT License
459 stars 186 forks source link

Can't use IsCursorCaptureEnabled from C# even after I did com import myself for IGraphicsCaptureSession2 #97

Closed gileli121 closed 2 years ago

gileli121 commented 2 years ago

Hello, For some reason, IGraphicsCaptureSession2 and IGraphicsCaptureSession3 are not available in C# but it is available in the docs here https://docs.microsoft.com/en-us/uwp/api/windows.graphics.capture.graphicscapturesession.iscursorcaptureenabled?view=winrt-22000

I was able to manually import this part myself -

I found this part in windows.graphics.capture.h

...
...
extern const __declspec(selectany) _Null_terminated_ WCHAR InterfaceName_Windows_Graphics_Capture_IGraphicsCaptureSession2[] = L"Windows.Graphics.Capture.IGraphicsCaptureSession2";
namespace ABI {
    namespace Windows {
        namespace Graphics {
            namespace Capture {
                MIDL_INTERFACE("2c39ae40-7d2e-5044-804e-8b6799d4cf9e")
                IGraphicsCaptureSession2 : public IInspectable
                {
                public:
                    virtual HRESULT STDMETHODCALLTYPE get_IsCursorCaptureEnabled(
                        boolean* value
                        ) = 0;
                    virtual HRESULT STDMETHODCALLTYPE put_IsCursorCaptureEnabled(
                        boolean value
                        ) = 0;
                };

                extern MIDL_CONST_ID IID& IID_IGraphicsCaptureSession2 = _uuidof(IGraphicsCaptureSession2);
            } /* Capture */
        } /* Graphics */
    } /* Windows */
} /* ABI */
....
....

So took the example here: https://github.com/microsoft/Windows.UI.Composition-Win32-Samples/tree/master/dotnet/WPF/ScreenCapture And under CaptureSampleCore I created this class:

namespace CaptureSampleCore.ComImports
{
    [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("2c39ae40-7d2e-5044-804e-8b6799d4cf9e")]
    [System.Security.SuppressUnmanagedCodeSecurity]
    public interface IGraphicsCaptureSession2
    {
        bool IsCursorCaptureEnabled { get; set; }
    }
}

Next, in file: https://github.com/microsoft/Windows.UI.Composition-Win32-Samples/blob/master/dotnet/WPF/ScreenCapture/CaptureSampleCore/BasicCapture.cs#L94

I added the following code:

public void StartCapture()
{
    // New Code - Start
    var pUnk = Marshal.GetIUnknownForObject(session);
    var session2 = Marshal.GetObjectForIUnknown(pUnk) as IGraphicsCaptureSession2;
    session2.IsCursorCaptureEnabled = false;
    // New Code - End

    session.StartCapture();
}

I did debug and found that it actually try to work because it was able to get session2 object and the debugger shows 2 properties that are true. It means that something until this point worked fine - it was able to cast it to IGraphicsCaptureSession2 and also read these properties: image

It even show extra property called IsBorderRequired. It is documented in the docs and I want to use it also.. but not for now. I don't know how it printed it because IGraphicsCaptureSession2 should not have it.. IGraphicsCaptureSession3 have it..

The program will crash when it will execute this line that it tries to set the value of IsCursorCaptureEnabled to false: image

Any idea what I am doing wrong here?

Thanks

gileli121 commented 2 years ago

COOL !! I did it! My mistake was here: image

It should be:

ComInterfaceType.InterfaceIsIInspectable

I found it by looking on windows.graphics.capture.h image

Now it works perfect!

gileli121 commented 2 years ago

This will disable the border!!

  1. Add:

    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIInspectable)]
    [Guid("f2cdd966-22ae-5ea1-9596-3a289344c3be")]
    public interface IGraphicsCaptureSession3
    {
    bool IsBorderRequired { get; set; }
    }
  2. Add the following code after you have session object and before the call to session.StartCapture(); :

var pUnk = Marshal.GetIUnknownForObject(session);
var session3 = Marshal.GetObjectForIUnknown(pUnk) as IGraphicsCaptureSession3;
session3.IsBorderRequired = false;

image

robmikh commented 2 years ago

I'm going to close this as you seem to have it figured out. To recap, all WinRT interfaces inherit from IInspectable, so keep that in mind when crafting those interface definitions by hand. I would recommend looking at the CsWinRT project to have them generated from metadata instead.

Second, a comment on the usage of the IsBorderRequired property. Before accessing this property, you should call GraphicsCaptureAccess.RequestAccessAsync and request access to the border. This doesn't make a huge difference for unpackaged Win32 applications, but is critical for packaged applications. Even if you are writing an unpackaged application, I still suggest calling it as it is the documented requirement.

EDIT: One more thing, it's safe to modify the IsBorderRequired property even if RequestAccessAsync says you don't have access. That just means the user preference or the system policy is currently set so that the border will always show up. But if it changes, the system will reevaluate using the value you placed in IsBorderRequired.

gileli121 commented 2 years ago

I'm going to close this as you seem to have it figured out. To recap, all WinRT interfaces inherit from IInspectable, so keep that in mind when crafting those interface definitions by hand. I would recommend looking at the CsWinRT project to have them generated from metadata instead.

Second, a comment on the usage of the IsBorderRequired property. Before accessing this property, you should call GraphicsCaptureAccess.RequestAccessAsync and request access to the border. This doesn't make a huge difference for unpackaged Win32 applications, but is critical for packaged applications. Even if you are writing an unpackaged application, I still suggest calling it as it is the documented requirement.

EDIT: One more thing, it's safe to modify the IsBorderRequired property even if RequestAccessAsync says you don't have access. That just means the user preference or the system policy is currently set so that the border will always show up. But if it changes, the system will reevaluate using the value you placed in IsBorderRequired.

You wrote:

but is critical for packaged applications.

robmikh

My project is Win32 app / WPF app that packaged as Microsoft Store app using this method: https://docs.microsoft.com/en-us/windows/msix/desktop/desktop-to-uwp-packaging-dot-net I tested it also while it is installed as "UWP" app and it worked fine without GraphicsCaptureAccess.RequestAccessAsync

So I don't understand why it is matters at all anyway.

robmikh commented 2 years ago

Please just follow the documentation. Otherwise, your app may break in the future, even if it appears to not do anything today.

gileli121 commented 2 years ago

@robmikh So you saying that the fact that it works for a packaged app is a bug? My app is declared with capability runFullTrust so I don't think that it is a bug.

This is the part about capabilities in my packaged app:


  <Applications>
    <Application Id="App"
      Executable="$targetnametoken$.exe"
      EntryPoint="$targetentrypoint$">
      <uap:VisualElements
        DisplayName="WindowTop"
        Description="UWP_PackagingProject"
        BackgroundColor="transparent"
        Square150x150Logo="Images\Square150x150Logo.png"
        Square44x44Logo="Images\Square44x44Logo.png">
        <uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png"  Square71x71Logo="Images\SmallTile.png" Square310x310Logo="Images\LargeTile.png"/>
        <uap:SplashScreen Image="Images\SplashScreen.png" />
        <uap:InitialRotationPreference>
          <uap:Rotation Preference="landscape"/></uap:InitialRotationPreference>
      </uap:VisualElements>

      <Extensions>
        <desktop:Extension
          Category="windows.startupTask"
          Executable="WindowTop\WindowTop.exe"
          EntryPoint="Windows.FullTrustApplication">
          <desktop:StartupTask
            TaskId="WindowTopStartupTask"
            Enabled="false"
            DisplayName="Start WindowTop with Windows" />
        </desktop:Extension>
      </Extensions>
    </Application>
  </Applications>

  <Capabilities>
    <Capability Name="internetClient" />
    <rescap:Capability Name="runFullTrust" />
  </Capabilities>

If I will call to GraphicsCaptureAccess.RequestAccessAsync, it will open dialog for the user (assuming that I added graphicsCaptureWithoutBorder capability) and the user will need to accept it?

robmikh commented 2 years ago

If you're running full trust, that would explain not seeing anything from RequestAccessAsync (although, again, you should still call it).

In the event that you would get a prompt, it would be a one time prompt to the user. All other times it would return the user's selected preference. This is similar to the way GPS or camera capabilities work.

gileli121 commented 2 years ago

If you're running full trust, that would explain not seeing anything from RequestAccessAsync (although, again, you should still call it).

OK, the current behavior considered as bug? And anyway, I should not get a prompt after RequestAccessAsync in case of full trust package?

robmikh commented 2 years ago

Not a bug, and yes I wouldn't expect the prompt to show up.

If that changes in the future, it would only be a prompt the user sees once for the duration that your application is installed.

gileli121 commented 2 years ago

Thank you @robmikh So if it is not bug, I am not going to use GraphicsCaptureAccess.RequestAccessAsync even if it suggested because if it work, don't break it.

And another question: How I can check if this property IsBorderRequired is available in the system? I can try to warp it in try-catch block but maybe there is better way

robmikh commented 2 years ago

That's a bad idea.

Use ApiInformation:

https://docs.microsoft.com/en-us/uwp/api/windows.foundation.metadata.apiinformation.ispropertypresent?view=winrt-22000