/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2017 Sri Harsha Chilakapati
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.shc.silenceengine.backend.lwjgl.glfw;
import com.shc.silenceengine.backend.lwjgl.glfw.callbacks.IMonitorCallback;
import com.shc.silenceengine.math.Vector2;
import org.lwjgl.BufferUtils;
import org.lwjgl.PointerBuffer;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWGammaRamp;
import org.lwjgl.glfw.GLFWMonitorCallback;
import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.system.MemoryUtil;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.lwjgl.glfw.GLFW.*;
/**
* <p> A monitor object represents a currently connected monitor and is represented as instance of the {@link Monitor}
* class. Monitor objects cannot be created or destroyed by the application and retain their addresses until the
* monitors they represent are disconnected or until the library is terminated.</p>
*
* <p> Each monitor has a current video mode, a list of supported video modes, a virtual position, a human-readable
* name, an estimated physical size and a gamma ramp. One of the monitors is the primary monitor. The virtual position
* of a monitor is in screen coordinates and, together with the current video mode, describes the viewports that the
* connected monitors provide into the virtual desktop that spans them.</p>
*
* @author Sri Harsha Chilakapati
* @see VideoMode
* @see GammaRamp
*/
public class Monitor
{
private static Map<Long, Monitor> registeredMonitors = new HashMap<>();
private static Monitor primary;
private static List<Monitor> monitors;
private static GLFWMonitorCallback glfwMonitorCallback;
private static IMonitorCallback monitorCallback;
private long handle;
private List<VideoMode> videoModes;
/**
* Constructs a wrapper Monitor object around the native pointer given by the {@link GLFW#glfwGetMonitors()} and
* {@link GLFW#glfwGetPrimaryMonitor()} functions.
*
* @param handle The handle that points to the GLFWMonitor* of the native monitor type.
*/
private Monitor(long handle)
{
this.handle = handle;
registeredMonitors.put(handle, this);
}
/**
* This function returns the primary monitor. This is usually the monitor where elements like the Windows task bar
* or the OS X menu bar is located.
*
* @return The Monitor object which represents the primary monitor of the OS.
*/
public static Monitor getPrimaryMonitor()
{
if (primary == null)
primary = new Monitor(glfwGetPrimaryMonitor());
return primary;
}
/**
* This function sets the monitor configuration callback, or removes the currently set callback. This is called when
* a monitor is connected to or disconnected from the system.
*
* @param callback The new callback, or {@code null} to remove the currently set callback.
*
* @return The previously set callback, or {@code null} if no callback was set or the library had not been
* initialized.
*/
public static IMonitorCallback setCallback(IMonitorCallback callback)
{
IMonitorCallback previousCallback = monitorCallback;
if (callback == null)
callback = (monitor, event) ->
{
};
monitorCallback = callback;
if (glfwMonitorCallback != null)
glfwMonitorCallback.free();
glfwMonitorCallback = GLFWMonitorCallback.create((monitor, event) ->
monitorCallback.invoke(registeredMonitors.get(monitor), event)
);
glfwSetMonitorCallback(glfwMonitorCallback);
return previousCallback;
}
/**
* This function returns a list of Monitor objects for all currently connected monitors. This list is unmodifiable.
*
* @return The list of all connected Monitor objects.
*/
public static List<Monitor> getMonitors()
{
if (monitors == null)
{
monitors = new ArrayList<>();
PointerBuffer buffer = glfwGetMonitors();
while (buffer.hasRemaining())
monitors.add(new Monitor(buffer.get()));
monitors = Collections.unmodifiableList(monitors);
}
return monitors;
}
/**
* This function returns a list of all video modes supported by the specified monitor. The returned list is sorted
* in ascending order, first by color bit depth (the sum of all channel depths) and then by resolution area (the
* product of width and height).
*
* @return The list of {@link VideoMode}s supported by this Monitor object. Note that this list is unmodifiable.
*/
public List<VideoMode> getVideoModes()
{
if (videoModes == null)
{
videoModes = new ArrayList<>();
GLFWVidMode.Buffer modes = glfwGetVideoModes(handle);
for (int i = 0; i < modes.capacity(); i++)
{
modes.position(i);
int width = modes.width();
int height = modes.height();
int redBits = modes.redBits();
int greenBits = modes.greenBits();
int blueBits = modes.blueBits();
int refreshRate = modes.refreshRate();
videoModes.add(new VideoMode(width, height, redBits, greenBits, blueBits, refreshRate));
}
videoModes = Collections.unmodifiableList(videoModes);
}
return videoModes;
}
/**
* This function returns the current video mode of the specified monitor. If you have created a full screen window
* for that monitor, the return value will depend on whether that window is iconified.
*
* @return The current {@link VideoMode} of this monitor.
*/
public VideoMode getVideoMode()
{
GLFWVidMode mode = glfwGetVideoMode(handle);
int width = mode.width();
int height = mode.height();
int redBits = mode.redBits();
int greenBits = mode.greenBits();
int blueBits = mode.blueBits();
int refreshRate = mode.refreshRate();
return new VideoMode(width, height, redBits, greenBits, blueBits, refreshRate);
}
/**
* This method returns the native GLFWmonitor pointer as a Java long value. This is required by the native GLFW
* functions that are wrapped in the {@link GLFW} class that take the pointer of the monitor to function.
*
* @return The native handle of this Monitor object.
*/
public long getHandle()
{
return handle;
}
/**
* This method generates a 256-element gamma ramp from the specified exponent and then calls {@link
* Monitor#setGammaRamp(GammaRamp)} with it. The value must be a finite number greater than zero.
*
* @param gamma The desired exponent.
*/
public void setGamma(float gamma)
{
glfwSetGamma(handle, gamma);
}
/**
* This method returns the current gamma ramp of the specified monitor.
*
* @return The current GammaRamp, or {@code null} if an error occurred.
*/
public GammaRamp getGammaRamp()
{
GLFWGammaRamp gammaRamp = glfwGetGammaRamp(handle);
if (gammaRamp.address() == 0)
return null;
ShortBuffer rBuffer = gammaRamp.red();
ShortBuffer gBuffer = gammaRamp.green();
ShortBuffer bBuffer = gammaRamp.blue();
short[] red = new short[gammaRamp.size()];
short[] green = new short[gammaRamp.size()];
short[] blue = new short[gammaRamp.size()];
int i = 0;
while (rBuffer.hasRemaining())
red[i++] = rBuffer.get();
i = 0;
while (gBuffer.hasRemaining())
green[i++] = gBuffer.get();
i = 0;
while (bBuffer.hasRemaining())
blue[i++] = bBuffer.get();
return new GammaRamp(red, green, blue);
}
/**
* This method sets the current gamma ramp for the specified monitor. The original gamma ramp for that monitor is
* saved by GLFW the first time this function is called and is restored by {@link GLFW3#terminate()}.
*
* @param gammaRamp The {@link GammaRamp} to use.
*/
public void setGammaRamp(GammaRamp gammaRamp)
{
GLFWGammaRamp ramp = GLFWGammaRamp.malloc();
ShortBuffer rBuffer = BufferUtils.createShortBuffer(gammaRamp.getSize());
ShortBuffer gBuffer = BufferUtils.createShortBuffer(gammaRamp.getSize());
ShortBuffer bBuffer = BufferUtils.createShortBuffer(gammaRamp.getSize());
rBuffer.put(gammaRamp.getRed()).flip();
gBuffer.put(gammaRamp.getGreen()).flip();
bBuffer.put(gammaRamp.getBlue()).flip();
ramp.red(rBuffer);
ramp.green(gBuffer);
ramp.blue(bBuffer);
ramp.size(gammaRamp.getSize());
glfwSetGammaRamp(handle, ramp);
ramp.free();
}
/**
* <p> This method returns the size, in millimetres, of the display area of the specified monitor.</p>
*
* <p> Some systems do not provide accurate monitor size information, either because the monitor EDID data is
* incorrect or because the driver does not report it accurately.</p>
*
* <p> If an error occurs, the size components will be set to zero.</p>
*
* @return The physical size of this monitor in millimeters as a Vector2. The x-component is the width and the
* y-component is the height of the monitor.
*/
public Vector2 getPhysicalSize()
{
IntBuffer sizeBuffer = BufferUtils.createIntBuffer(2);
long addressWidth = MemoryUtil.memAddress(sizeBuffer);
long addressHeight = addressWidth + Integer.BYTES;
nglfwGetMonitorPhysicalSize(handle, addressWidth, addressHeight);
Vector2 size = new Vector2(sizeBuffer.get(0), sizeBuffer.get(1));
return size;
}
/**
* This method returns the position, in screen coordinates, of the upper-left corner of the specified monitor. If
* there is any error, the position is set to zero, or the origin.
*
* @return The virtual position of this monitor.
*/
public Vector2 getVirtualPosition()
{
IntBuffer posBuffer = BufferUtils.createIntBuffer(2);
long posX = MemoryUtil.memAddress(posBuffer);
long posY = posX + Integer.BYTES;
nglfwGetMonitorPos(handle, posX, posY);
Vector2 position = new Vector2(posBuffer.get(0), posBuffer.get(1));
return position;
}
@Override
public int hashCode()
{
return (int) (handle ^ (handle >>> 32));
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Monitor monitor = (Monitor) o;
return handle == monitor.handle;
}
@Override
public String toString()
{
return "Monitor{" +
"handle=" + handle + ", " +
"name=\"" + getName() + "\", " +
"primary=" + isPrimary() +
'}';
}
/**
* This method returns a human-readable name, encoded as UTF-8, of the specified monitor. The name typically
* reflects the make and model of the monitor and is not guaranteed to be unique among the connected monitors.
*
* @return The name of this monitor object.
*/
public String getName()
{
return glfwGetMonitorName(handle);
}
/**
* This method is used to check if this monitor object is the primary monitor of the OS or not.
*
* @return True if this monitor is primary, else False.
*/
public boolean isPrimary()
{
return getMonitors().get(0).equals(this);
}
}