StrokesPlus.net
Welcome Guest! To enable all features please Login or Register.

Notification

Icon
Error

Options
Go to last post Go to first unread
niczoom  
#1 Posted : Wednesday, February 10, 2021 4:48:11 AM(UTC)
niczoom

Rank: Newbie

Reputation:

Groups: Approved
Joined: 2/5/2021(UTC)
Posts: 5
Australia

Thanks: 6 times
I have a multi-monitor setup using 3 screens. One screen is running at 125% scale, the others at 100%. For this example, when I use the following code to move a window from my left screen (125%) to the left half of my centre screen(100%) the window is smaller than it should be, by a factor of 1.25, the scale factor of the left screen.

Code:
 // Set the windows position & size.
    var wndRect = action.Window.Rectangle
    wndRect.X = Xpos
    wndRect.Y = objToScreen.Bounds.Y
    wndRect.Width = Width
    wndRect.Height = objToScreen.WorkingArea.Height
    action.Window.Rectangle = wndRect

The numbers used in the code above were 'Width=1280' and the 'Height=1400'.

After this I ran a separate script:
Code:
var wnd = sp.ForegroundWindow()
var wndRect = wnd.Rectangle
osd("wndRect.X="+wndRect.X+
        "\nwndRect.Y="+wndRect.Y+
        "\nwndRect.Width="+wndRect.Width+
        "\nwndRect.Height="+wndRect.Height)

Which now showed the Width=1023 and the Height=1119, if you multiply these by 1.25 you get, within a few pixels, the expected numbers.

And vice versa going from the 100% screen back to the 125% screen has the opposite effect of making the window 1.25 times to big for the width and height, even using the correct dimensions.

Reading up about this (LINK) there are some windows functions that can return the screen dpi and perhaps this can be utilized to overcome this issue?

Any ideas? Full code is below, it runs perfectly when all screens are set to 100% scale.

Code:
function FlingToNext(Direction) {
    
    // Get current screen (monitor) DeviceName
    var currentScreenDeviceName = action.Window.Screen.DeviceName

    // Loop through all screens (monitors)
    var allScreens = Screen.AllScreens // Return an array of all screen objects.
    
    // Test 'Direction' for the text 'Half', this indicates positioning the window on either the left or right of the SAME screen.
    if (Direction.includes("Half")) {
        var objToScreen = action.Window.Screen
    }
    else // Send window to another screen.
    {
        // Loop through all screens
        for (i = 0; i < allScreens.Length; i++) {

            // Select a case based on the number of screens currently plugged into this computer.
            switch(allScreens.Length) {

                case 1:
                    var objToScreen = allScreens[1]
                    break

                case 2:
                    if (currentScreenDeviceName == allScreens[i])  {
                        var screenIndex = (i=0) ? 1 : 0
                        var objToScreen = allScreens[screenIndex]
                    }
                    break

                case 3:
                    if (currentScreenDeviceName == allScreens[i].DeviceName)  {
                        // If the current screen is the FIRST in the array.
                        // If ToNextScreen: add 1 for the next screen, Else ToLeft: return the last screen index [.Length]
                        if (i == 0) {
                            var screenIndex = (Direction == "ToNextScreen") ? i+1 : allScreens.Length-1
                           //sp.MessageBox("TO: screenIndex="+screenIndex, "FIRST")
                        }

                        // If the current screen is the LAST in the arrayt.
                        // If ToNextScreen: return the first [0] screen index, Else ToLeft: minus 1 for previous screen.
                        if (allScreens.Length-1 == i) {
                            var screenIndex = (Direction == "ToNextScreen") ? 0 : i-1
                            //sp.MessageBox("TO: screenIndex="+screenIndex, "LAST")
                        }

                        // If the current screen is not first OR last in the array.
                        // If ToNextScreen: add 1 for the next screen, Else ToLeft: minus 1 for the previous screen.
                        if ( (allScreens.Length-1 != i) && (i != 0) ) {
                            var screenIndex = (Direction == "ToNextScreen") ? i+1 : i-1
                            //sp.MessageBox("To: screenIndex="+screenIndex, "MIDDLE")
                        }
                    } // if
            } // switch
            var objToScreen = allScreens[screenIndex]
        } // for
    } // if
 
    // Check if the screen is in a portrait position .. [Portrait=True]
    var Portrait = (objToScreen.WorkingArea.Width < objToScreen.WorkingArea.Height) ? 1 : 0
    // Set the windows X pos to the left or right half of the (landscape) screen depending if moving from R->L or L->R.
    var Xpos = (Direction == ("ToNextScreen") || Direction == ("ToLeftHalf")) ? objToScreen.Bounds.X : objToScreen.Bounds.X + objToScreen.Bounds.Width/2
    // If the screens in a 'Portrait' orientation the set the windows Xpos always to the left, else keep the current Xpos.
    var Xpos = (Portrait) ? objToScreen.Bounds.X : Xpos
    // If the screens in a 'Portrait' orientation then set the window to the full screen width, else make it half the screen width.
    var Width = (Portrait) ? objToScreen.WorkingArea.Width : objToScreen.WorkingArea.Width/2
    
    // Set the windows position & size.
    var wndRect = action.Window.Rectangle
    wndRect.X = Xpos
    wndRect.Y = objToScreen.Bounds.Y
    wndRect.Width = Width
    wndRect.Height = objToScreen.WorkingArea.Height
    action.Window.Rectangle = wndRect
    
    var wah = objToScreen.WorkingArea.Height
    // osd() is a simple function to display data.
    osd("Width="+Width+"\nwah="+objToScreen.WorkingArea.Height)
}
Rob  
#2 Posted : Wednesday, February 10, 2021 10:57:17 AM(UTC)
Rob

Rank: Administration

Reputation:

Groups: Translators, Members, Administrators
Joined: 1/11/2018(UTC)
Posts: 1,359
United States
Location: Tampa, FL

Thanks: 28 times
Was thanked: 419 time(s) in 356 post(s)
What happens if you move the window to the target screen first, then assign the rectangle?
I wonder if Windows performs scaling based on the current screen on which the app resides
Code:
    // Set the windows position & size.

    action.Window.Location = new Point(Xpos,objToScreen.Bounds.Y);

    var wndRect = action.Window.Rectangle
    wndRect.X = Xpos
    wndRect.Y = objToScreen.Bounds.Y
    wndRect.Width = Width
    wndRect.Height = objToScreen.WorkingArea.Height
    action.Window.Rectangle = wndRect

Also, for Windows 10 1607 or greater, you can use this API call to get the window's DPI. I set one of my screens to 125% and it reported 120 (125%), but 96 (100%) for my others.
Code:
if(!NativeModules.User32)
{
    var IntPtrT = host.typeOf(clr.System.IntPtr);
    var UInt32T = host.typeOf(clr.System.UInt32);

    //--------------------------------------------------------------------
    // Define the type which will contain the PInvokes
    // Type can still be modified until .Create() is called
    //--------------------------------------------------------------------
	
    var user32TB = sp.NativeModule().DefineType("User32", "Class,Public,SequentialLayout,Serializable");
	
	// Define PInvoke method for GetDpiForWindow (Windows 10 1607 or greater)
	
	user32TB.DefinePInvokeMethod("GetDpiForWindow",
								 "user32.dll",
								 [IntPtrT], 
								 UInt32T, 
								 "PreserveSig");	

    //--------------------------------------------------------------------
    // Creates the type (which cannot be changed after) and refreshes the 
    // NativeModules assembly in the script engine
    //--------------------------------------------------------------------
	user32TB.Create();
}

var wndDPI = NativeModules.User32.GetDpiForWindow(action.Window.HWnd);
sp.MessageBox(`Window DPI: ${wndDPI}`, "DPI");

See this post for more info about native API calls:
https://forum.strokesplus.net/posts/m11783-Example---Test-Code-for-Native-Binding
niczoom  
#3 Posted : Thursday, February 11, 2021 1:59:12 AM(UTC)
niczoom

Rank: Newbie

Reputation:

Groups: Approved
Joined: 2/5/2021(UTC)
Posts: 5
Australia

Thanks: 6 times
Thanks for the reply, Rob.

Yes, setting the location first did ultimately work!
Quote:
I wonder if Windows performs scaling based on the current screen on which the app resides

So when a window is either dragged or moved between screens of a different Scale/DPI windows automatically changes the width & height of that window to match the new Scale/DPI relative to the screen it has moved from. Using 'Rectangle' to change both location & size at once seemed to happen before windows knew the window moved to a new screen, it then auto-sized the window on the new screen relative to the previous screens DPI.

So in this instance setting the location first then setting the Width & Height next works!

A couple of things to note. When dragging most windows to & forth between my 125%<->100% scaled screens, all windows (bar Explorer) cleanly and quickly auto-resize. Explorer takes more time to adjust, it goes through and reorganizes itself, enlarging icons & layout etc. What's interesting is that your 'StrokesPlus.net' program just glides through without auto-resizing itself, as far as I can tell. So Im assuming other windows are programmed to react in a change in screen DPI/Scale and adjust accordingly, some take longer than others.

Thanks for the code to get 'GetDpiForWindow' working! I did see this in MSDN but had no idea on how to implement it in StrokePlus. I use AutoHotKey a lot and this seems similar to using 'DllCall', although looks to be a bit more work involved.

Where do I pick up more information on things like 'host.typeOf' & 'clr.System...' etc. 'host.' has intellisense but typing 'clr.' doesn't, is it supposed to? Or is it just understanding C# more and how its implemented in StrokesPlus?

Anyway thanks for your help.
Rob  
#4 Posted : Thursday, February 11, 2021 3:20:45 AM(UTC)
Rob

Rank: Administration

Reputation:

Groups: Translators, Members, Administrators
Joined: 1/11/2018(UTC)
Posts: 1,359
United States
Location: Tampa, FL

Thanks: 28 times
Was thanked: 419 time(s) in 356 post(s)
Because S+ needs to always have the exact (non-scaled) coordinates to behave as expected (and draw gestures in the right place!), I have entries in the application manifest that tells Windows that S+ will manage the DPI on its own, so don't scale anything:

Code:
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness>


Yes, the DllCall equivalents are a bit more verbose here because .NET is a managed environment, where AHK is native code. .NET requires a bit more marshalling between managed and native code - plus I wanted to make this flexible to support things like callbacks, sequential typedefs, etc. Also, we have another layer of translation with JavaScript to .NET as well.

For host, that's the provided functions from Microsoft ClearScript:

https://microsoft.github.io/ClearScript/Reference/html/Methods_T_Microsoft_ClearScript_HostFunctions.htm

Their FAQtorial (basics):

https://microsoft.github.io/ClearScript/Tutorial/FAQtorial

The ClearScript repo:

https://github.com/microsoft/ClearScript

ClearScript is what provides the bridge between JavaScript and C#/.NET, translating objects back and forth, exposing sp.* functions, etc.

If you look in the Script Help window, the first entry .NET and ClearScript (poorly) explains the mapping of the objects exposed to the script engine/JavaScript.

So clr is for all classes within the mscorlib, System, and System.Core (base .NET Framework) classes.

For example, see this MSDN page:

https://docs.microsoft.com/en-us/dotnet/api/system.int32?view=netframework-4.8

Notice in the header area it reads: Assembly: mscorlib.dll
So the Int32 type (struct) would be within the clr object.

Note that you still have to fully qualify the namespace (unless you alias that separately).

Another example, in S+ script forms.System.Windows.Forms.MessageBox translates to this:

https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.messagebox?view=netframework-4.8

Intellisense is really a big pain, so not much actually has it, lol. And the .NET framework is massive, so intellisense would be very cumbersome and sluggish without some huge caching.

Also, check out this post:

https://forum.strokesplus.net/posts/t5028-Debugging-Scripts

I often find that using Chrome to hit a debugger breakpoint in scripts, then using Watch / Console commands in Dev Tools really helps to discover a lot of things going on under the hood and available.

Also, if you're researching anything in the .NET docs, make sure that you're only looking at .NET Framework 4.8, as that's what S+ is currently referencing. Meaning, there are some new things in .NET 5.0 which may not be available for .NET Framework 4.8.

Let me know if you have any other questions!

Edited by user Thursday, February 11, 2021 3:24:02 AM(UTC)  | Reason: Not specified

thanks 1 user thanked Rob for this useful post.
niczoom on 2/11/2021(UTC)
niczoom  
#5 Posted : Thursday, February 11, 2021 3:33:45 AM(UTC)
niczoom

Rank: Newbie

Reputation:

Groups: Approved
Joined: 2/5/2021(UTC)
Posts: 5
Australia

Thanks: 6 times
Thanks Rob! BigGrin

The information in your reply helped a lot about understanding how S+ scripting works. And the tip/link on debugging will come in useful as I was wondering about that.
Users browsing this topic
Guest
Forum Jump  
You cannot post new topics in this forum.
You cannot reply to topics in this forum.
You cannot delete your posts in this forum.
You cannot edit your posts in this forum.
You cannot create polls in this forum.
You cannot vote in polls in this forum.