Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Programmatically rotate monitor

Tags:

python

winapi

I'm working on making a utility script that does a whole bunch of things. One of the things I want to do is to rotate a display; I have multiple monitors, and I want the main one to rotate. I know this sort of thing usually works through win32api and I found a few functions there that seem helpful, but I'm struggling with the implementation.

Everything between this line and the next is out of date, see below second line for up to date description of solution attempts


After burying my face in the docs, I'm afraid I still don't have much of an idea on how to move forward aside from that it will likely involve win32api.ChangeDisplaySettingsEx(). I know I need to give that function a pointer to a DEVMODE object (not even sure how to do C pointers in python), which I think I can get from win32api.EnumDisplaySettingsEx(). So if I try,

>>> import win32api as win32
>>> a = win32.EnumDisplayDevices()
>>> type(a)

I should get something that involves DEVMODE pointer or whatever, but instead I get

>>> type(a)
<type 'PyDISPLAY_DEVICE'>

And I have no idea what to do with that, but I think this is the structure

So, how do I get a DEVMODE ojbect I can give to ChangeDisplaySettingsEx() so that I can rotate one of my displays? Thanks in advance.

I'm running Python 2.7 on Windows 7

EDIT: If I acutally use the correct function, it still doesn't work. Could this be the Python module be not complete?

>>> a = win32.EnumDisplaySettings()
>>> type(a)
<type 'PyDEVMODEA'>
>>> a.dmSize
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'PyDEVMODEA' object has no attribute 'dmSize'
>>> a.dmScale
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'PyDEVMODEA' object has no attribute 'dmScale'
>>> a.dmDisplayOrientation
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'PyDEVMODEA' object has no attribute 'dmDisplayOrientation'

Now, I noticed that this is giving me a DEVMODEA object instead of DEVMODE, but this page says they're the same. What could be the issue here?

EDIT: Now that I'm using the correct attribute names, I can get a valid DEVMODEA object:

>>> a = win32.EnumDisplaySettings()
>>> a.Size
124L
>>> a.DisplayOrientation
0L

And change the orientation in the object:

>>> a.DisplayOrientation = 90L
>>> a.DisplayOrientation
90L

I can then attempt to "apply" these changes by giving this DEVMODEA object to ChangeDisplaySettingsEx()

>>> win32.ChangeDisplaySettingsEx(a.DeviceName, a, 0)
-5L

This does nothing. Unfortnately, the docs aren't very useful in helping me interpret the return value. I'm assuming -5L is some sort of error code since it didn't work, but I have no way of knowing which one. What does this return value mean and how do I get my new DEVMODEA object to "apply"

I've worked out that -5L return value indicates a bad parameter. Specifically, its angry at the first field. If I replace that with DISPLAY_DEVICE.DeviceName, I get a -2L result. This corresponds to a bad mode (whatever that is). This happens even if I give ChangeDisplaySettingsEx() exactly what EnumDisplaySettings() puts out.


So my progress so far:

>>> import win32api as win32
>>> import win32con
>>> a = win32.EnumDisplaySettings()
>>> a.DisplayOrientation
0L
>>> a.DisplayOrientation = win32con.DMDO_90
>>> a.DisplayOrientation
1L
>>> a.PelsWidth, a.PelsHeight = a.PelsHeight, a.PelsWidth
>>> a.Fields = a.Fields & win32con.DM_DISPLAYORIENTATION
>>> name = win32.EnumDisplayDevices().DeviceName
>>> name
'\\\\.\\DISPLAY1'
>>> win32.ChangeDisplaySettingsEx(name, a)
-2L

Most recent attempt (6/4, 10:50am EST)

Python 2.7.6 (default, Nov 10 2013, 19:24:18) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import win32api as win32
>>> import win32con
>>>
>>> device = win32.EnumDisplayDevices(None, 1)
>>> print "Rotate device {} ({})".format(device.DeviceString, device.DeviceName)
Rotate device Intel(R) HD Graphics 4000 (\\.\DISPLAY2)
>>>
>>> dm = win32.EnumDisplaySettings(device.DeviceName, win32con.ENUM_CURRENT_SETTINGS)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
pywintypes.error: (0, 'EnumDisplaySettings', 'No error message is available')
>>>
>>> dm = win32.EnumDisplaySettings(device.DeviceName)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
pywintypes.error: (123, 'EnumDisplaySettings', 'The filename, directory name, or volume label syntax is incorrect.')
>>>
>>> dm = win32.EnumDisplaySettings(device.DeviceName, win32con.ENUM_CURRENT_SETTINGS)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
pywintypes.error: (123, 'EnumDisplaySettings', 'The filename, directory name, or volume label syntax is incorrect.')
>>>

Notice how the first and third attempt to create dm fail with different errors despite being the same code.

like image 494
wnnmaw Avatar asked May 20 '14 19:05

wnnmaw


2 Answers

You are calling the EnumDisplayDevices API that returns a PDISPLAY_DEVICE. (see http://msdn.microsoft.com/en-us/library/windows/desktop/dd162609(v=vs.85).aspx)

You can obtain the object in two way:

1) From EnumDisplayDevicesEx

>>> import win32api
>>> win32api.EnumDisplaySettingsEx()
<PyDEVMODEA object at 0x00512E78>

2) or creating it (it will be empty)

>>> import pywintypes;
>>> dmode = pywintypes.DEVMODEType()
>>> type(dmode)
<type 'PyDEVMODEA'>

EDIT:

the property exposed by the object are not named like the "win32" version: for example dmSize becomes Size the full list can be seen with dir(dmode) command on PyDEVMODEA object.

The description of the fields can be read with the help(dmode) command on a PyDEVMODEA object.

For a full detailed mapping refer to PyDEVMODE.cpp inside the pywin32 source distribution

EDIT2: The procedure to rotate the monitor is:

  1. Get Devmode

  2. Get DisplayName

  3. Set Rotation

  4. Configure the flags with a.Fields = a.Fields & win32con.DM_DISPLAYORIENTATION

  5. Invoke ChangeDisplaySettingsEx

In your script you are missing steps from 3-5. (also Fields is used as a binary mask so must handled corrispondly).

You can see a complete example about the api usage (in C) on the msdn site: http://msdn.microsoft.com/en-us/library/ms812499.aspx

Edit3 During "Set Rotation" you must also need to swap width and height, otherwise you are asking an screen mode that is not possible:

(dmode.PelsWidth,dmode.PelsHeight) = (dmode.PelsHeight,dmode.PelsWidth)

EDIT4 A complete example (with no error checking):

import win32api as win32
import win32con

def printAllScreen():
    i = 0
    while True:
        try:
            device = win32.EnumDisplayDevices(None,i);
            print("[%d] %s (%s)"%(i,device.DeviceString,device.DeviceName));
            i = i+1;
        except:
            break;
    return i

screen_count=printAllScreen()
x = int(input("\nEnter a display number [0-%d]: "%screen_count-1))


device = win32.EnumDisplayDevices(None,x);
print("Rotate device %s (%s)"%(device.DeviceString,device.DeviceName));

dm = win32.EnumDisplaySettings(device.DeviceName,win32con.ENUM_CURRENT_SETTINGS)
dm.DisplayOrientation = win32con.DMDO_90
dm.PelsWidth, dm.PelsHeight = dm.PelsHeight, dm.PelsWidth
dm.Fields = dm.Fields & win32con.DM_DISPLAYORIENTATION
win32.ChangeDisplaySettingsEx(device.DeviceName,dm)
like image 79
lbenini Avatar answered Nov 18 '22 13:11

lbenini


My answer is a bit hacky but it works. It is based on the fact that the screen rotates when the special combination of keys is pressed by the user.

from win32api import keybd_event

def rotate_screen(orientation):
    vals = dict(zip(['left', 'up', 'right', 'down'],
                    [37, 38, 39, 40])) 
    comb = 165, vals[orientation]
    for k in comb:
        keybd_event(k, 0, 1, 0)
    for k in reversed(comb):
        keybd_event(k, 0, 2, 0)

Example:

rotate_screen('down')

Note: Ibenini's approach is obviously better because it is more reliable. I posted this method only as an example of a simpler way of doing that.

like image 22
Piotr Dabkowski Avatar answered Nov 18 '22 15:11

Piotr Dabkowski