Opentk/Source/OpenTK/Platform/X11/X11XrandrDisplayDevice.cs

299 lines
13 KiB
C#
Raw Normal View History

#region --- License ---
/* Licensed under the MIT/X11 license.
* Copyright (c) 2006-2008 the OpenTK Team.
* This notice may not be removed from any source distribution.
* See license.txt for licensing detailed licensing details.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
namespace OpenTK.Platform.X11
{
internal class X11XrandrDisplayDevice : IDisplayDeviceDriver
{
static object display_lock = new object();
// Store a mapping between resolutions and their respective
// size_index (needed for XRRSetScreenConfig). The size_index
// is simply the sequence number of the resolution as returned by
// XRRSizes. This is done per available screen.
static List<Dictionary<DisplayResolution, int>> screenResolutionToIndex =
new List<Dictionary<DisplayResolution, int>>();
// Store a mapping between DisplayDevices and their default resolutions.
static Dictionary<DisplayDevice, int> deviceToDefaultResolution = new Dictionary<DisplayDevice, int>();
// Store a mapping between DisplayDevices and X11 screens.
static Dictionary<DisplayDevice, int> deviceToScreen = new Dictionary<DisplayDevice, int>();
// Keep the time when the config of each screen was last updated.
static List<IntPtr> lastConfigUpdate = new List<IntPtr>();
#region --- Constructors ---
static X11XrandrDisplayDevice()
{
using (new XLock(API.DefaultDisplay))
{
List<DisplayDevice> devices = new List<DisplayDevice>();
bool xinerama_supported = false;
try
{
// Try to use Xinerama to obtain the geometry of all output devices.
int event_base, error_base;
if (NativeMethods.XineramaQueryExtension(API.DefaultDisplay, out event_base, out error_base) &&
NativeMethods.XineramaIsActive(API.DefaultDisplay))
{
IList<XineramaScreenInfo> screens = NativeMethods.XineramaQueryScreens(API.DefaultDisplay);
bool first = true;
foreach (XineramaScreenInfo screen in screens)
{
DisplayDevice dev = new DisplayDevice();
dev.Bounds = new Rectangle(screen.X, screen.Y, screen.Width, screen.Height);
if (first)
{
// We consider the first device returned by Xinerama as the primary one.
// Makes sense conceptually, but is there a way to verify this?
dev.IsPrimary = true;
first = false;
}
devices.Add(dev);
// It seems that all X screens are equal to 0 is Xinerama is enabled, at least on Nvidia (verify?)
deviceToScreen.Add(dev, 0 /*screen.ScreenNumber*/);
xinerama_supported = true;
}
}
}
catch { Debug.Print("Xinerama query failed."); }
if (!xinerama_supported)
{
// We assume that devices are equivalent to the number of available screens.
// Note: this won't work correctly in the case of distinct X servers.
for (int i = 0; i < API.ScreenCount; i++)
{
DisplayDevice dev = new DisplayDevice();
dev.IsPrimary = i == Functions.XDefaultScreen(API.DefaultDisplay);
devices.Add(dev);
deviceToScreen.Add(dev, i);
}
}
// Get available resolutions. Then, for each resolution get all available rates.
foreach (DisplayDevice dev in devices)
{
int screen = deviceToScreen[dev];
IntPtr timestamp_of_last_update;
Functions.XRRTimes(API.DefaultDisplay, screen, out timestamp_of_last_update);
lastConfigUpdate.Add(timestamp_of_last_update);
List<DisplayResolution> available_res = new List<DisplayResolution>();
// Add info for a new screen.
screenResolutionToIndex.Add(new Dictionary<DisplayResolution, int>());
int[] depths = FindAvailableDepths(screen);
int resolution_count = 0;
foreach (XRRScreenSize size in FindAvailableResolutions(screen))
{
if (size.Width == 0 || size.Height == 0)
{
Debug.Print("[Warning] XRandR returned an invalid resolution ({0}) for display device {1}", size, screen);
continue;
}
short[] rates = null;
rates = Functions.XRRRates(API.DefaultDisplay, screen, resolution_count);
// It seems that XRRRates returns 0 for modes that are larger than the screen
// can support, as well as for all supported modes. On Ubuntu 7.10 the tool
// "Screens and Graphics" does report these modes, though.
foreach (short rate in rates)
{
// Note: some X servers (like Xming on Windows) do not report any rates other than 0.
// If we only have 1 rate, add it even if it is 0.
if (rate != 0 || rates.Length == 1)
foreach (int depth in depths)
available_res.Add(new DisplayResolution(0, 0, size.Width, size.Height, depth, (float)rate));
}
// Keep the index of this resolution - we will need it for resolution changes later.
foreach (int depth in depths)
{
// Note that Xinerama may return multiple devices for a single screen. XRandR will
// not distinguish between the two as far as resolutions are supported (since XRandR
// operates on X screens, not display devices) - we need to be careful not to add the
// same resolution twice!
DisplayResolution res = new DisplayResolution(0, 0, size.Width, size.Height, depth, 0);
if (!screenResolutionToIndex[screen].ContainsKey(res))
screenResolutionToIndex[screen].Add(res, resolution_count);
}
++resolution_count;
}
// The resolution of the current DisplayDevice is discovered through XRRConfigCurrentConfiguration.
// Its refresh rate is discovered by the FindCurrentRefreshRate call.
// Its depth is discovered by the FindCurrentDepth call.
float current_refresh_rate = FindCurrentRefreshRate(screen);
int current_depth = FindCurrentDepth(screen);
IntPtr screen_config = Functions.XRRGetScreenInfo(API.DefaultDisplay, Functions.XRootWindow(API.DefaultDisplay, screen));
ushort current_rotation; // Not needed.
int current_resolution_index = Functions.XRRConfigCurrentConfiguration(screen_config, out current_rotation);
if (dev.Bounds == Rectangle.Empty)
dev.Bounds = new Rectangle(0, 0, available_res[current_resolution_index].Width, available_res[current_resolution_index].Height);
dev.BitsPerPixel = current_depth;
dev.RefreshRate = current_refresh_rate;
dev.AvailableResolutions = available_res;
deviceToDefaultResolution.Add(dev, current_resolution_index);
}
}
}
internal X11XrandrDisplayDevice() { }
#endregion
#region --- Private Methods ---
#region static int[] FindAvailableDepths(int screen)
static int[] FindAvailableDepths(int screen)
{
return Functions.XListDepths(API.DefaultDisplay, screen);
}
#endregion
#region static XRRScreenSize[] FindAvailableResolutions(int screen)
static XRRScreenSize[] FindAvailableResolutions(int screen)
{
XRRScreenSize[] resolutions = null;
resolutions = Functions.XRRSizes(API.DefaultDisplay, screen);
if (resolutions == null)
throw new NotSupportedException("XRandR extensions not available.");
return resolutions;
}
#endregion
#region static float FindCurrentRefreshRate(int screen)
static float FindCurrentRefreshRate(int screen)
{
short rate = 0;
IntPtr screen_config = Functions.XRRGetScreenInfo(API.DefaultDisplay, Functions.XRootWindow(API.DefaultDisplay, screen));
ushort rotation = 0;
int size = Functions.XRRConfigCurrentConfiguration(screen_config, out rotation);
rate = Functions.XRRConfigCurrentRate(screen_config);
Functions.XRRFreeScreenConfigInfo(screen_config);
return (float)rate;
}
#endregion
#region private static int FindCurrentDepth(int screen)
private static int FindCurrentDepth(int screen)
{
return (int)Functions.XDefaultDepth(API.DefaultDisplay, screen);
}
#endregion
#endregion
#region --- IDisplayDeviceDriver Members ---
public bool TryChangeResolution(DisplayDevice device, DisplayResolution resolution)
{
// If resolution is null, restore the default resolution (new_resolution_index = 0).
using (new XLock(API.DefaultDisplay))
{
int screen = deviceToScreen[device];
IntPtr root = Functions.XRootWindow(API.DefaultDisplay, screen);
IntPtr screen_config = Functions.XRRGetScreenInfo(API.DefaultDisplay, root);
ushort current_rotation;
int current_resolution_index = Functions.XRRConfigCurrentConfiguration(screen_config, out current_rotation);
int new_resolution_index;
if (resolution != null)
new_resolution_index = screenResolutionToIndex[screen]
[new DisplayResolution(0, 0, resolution.Width, resolution.Height, resolution.BitsPerPixel, 0)];
else
new_resolution_index = deviceToDefaultResolution[device];
Debug.Print("Changing size of screen {0} from {1} to {2}",
screen, current_resolution_index, new_resolution_index);
return 0 == Functions.XRRSetScreenConfigAndRate(API.DefaultDisplay, screen_config, root, new_resolution_index,
current_rotation, (short)(resolution != null ? resolution.RefreshRate : 0), lastConfigUpdate[screen]);
}
}
public bool TryRestoreResolution(DisplayDevice device)
{
return TryChangeResolution(device, null);
//System.Diagnostics.Process.Start("xrandr", "-s -0").WaitForExit(); // Hack, but works ;)
}
#endregion
#region NativeMethods
static class NativeMethods
{
const string Xinerama = "libXinerama";
[DllImport(Xinerama)]
public static extern bool XineramaQueryExtension(IntPtr dpy, out int event_basep, out int error_basep);
[DllImport(Xinerama)]
public static extern int XineramaQueryVersion (IntPtr dpy, out int major_versionp, out int minor_versionp);
[DllImport(Xinerama)]
public static extern bool XineramaIsActive(IntPtr dpy);
[DllImport(Xinerama)]
static extern IntPtr XineramaQueryScreens(IntPtr dpy, out int number);
public static IList<XineramaScreenInfo> XineramaQueryScreens(IntPtr dpy)
{
int number;
IntPtr screen_ptr = XineramaQueryScreens(dpy, out number);
List<XineramaScreenInfo> screens = new List<XineramaScreenInfo>(number);
unsafe
{
XineramaScreenInfo* ptr = (XineramaScreenInfo*)screen_ptr;
while (--number >= 0)
{
screens.Add(*ptr);
ptr++;
}
}
return screens;
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct XineramaScreenInfo
{
public int ScreenNumber;
public short X;
public short Y;
public short Width;
public short Height;
}
#endregion
}
}