///|
pub fn modes_from_edid(edid : Edid) -> Array[DisplayMode] {
let modes : Array[DisplayMode] = []
append_detailed_modes(modes, edid)
append_standard_modes(modes, edid)
append_established_modes(modes, edid)
unique_modes(modes)
}
///|
pub fn preferred_mode(edid : Edid) -> DisplayMode? {
for mode in modes_from_edid(edid) {
if mode.preferred {
return Some(mode)
}
}
best_mode(edid)
}
///|
pub fn best_mode(edid : Edid) -> DisplayMode? {
let modes = modes_from_edid(edid)
if modes.is_empty() {
None
} else {
let mut best = modes[0]
for mode in modes {
if mode_score(mode) > mode_score(best) {
best = mode
}
}
Some(best)
}
}
///|
pub fn modes_at_least(
edid : Edid,
width : Int,
height : Int,
refresh_hz : Int,
) -> Array[DisplayMode] {
let found : Array[DisplayMode] = []
let refresh_millihz = refresh_hz * 1000
for mode in modes_from_edid(edid) {
if mode.width >= width &&
mode.height >= height &&
mode.refresh_millihz >= refresh_millihz {
found.push(mode)
}
}
found
}
///|
pub fn modes_by_resolution(
edid : Edid,
width : Int,
height : Int,
) -> Array[DisplayMode] {
let found : Array[DisplayMode] = []
for mode in modes_from_edid(edid) {
if mode.width == width && mode.height == height {
found.push(mode)
}
}
found
}
///|
pub fn supports_resolution(edid : Edid, width : Int, height : Int) -> Bool {
!modes_by_resolution(edid, width, height).is_empty()
}
///|
pub fn supports_mode(
edid : Edid,
width : Int,
height : Int,
refresh_hz : Int,
) -> Bool {
!modes_at_least(edid, width, height, refresh_hz).is_empty()
}
///|
pub fn max_refresh_for_resolution(
edid : Edid,
width : Int,
height : Int,
) -> Int? {
let modes = modes_by_resolution(edid, width, height)
if modes.is_empty() {
None
} else {
let mut best = modes[0].refresh_hz_rounded()
for mode in modes {
if mode.refresh_hz_rounded() > best {
best = mode.refresh_hz_rounded()
}
}
Some(best)
}
}
///|
pub fn has_interlaced_modes(edid : Edid) -> Bool {
for mode in modes_from_edid(edid) {
if mode.interlaced {
return true
}
}
false
}
///|
pub fn mode_to_text(mode : DisplayMode) -> String {
let buf = StringBuilder(size_hint=96)
buf.write_string(mode.label())
buf.write_string(" ")
buf.write_string(mode.source.name())
if mode.preferred {
buf.write_string(" preferred")
}
if mode.interlaced {
buf.write_string(" interlaced")
}
match mode.pixel_clock_khz {
Some(clock) => {
buf.write_string(" ")
buf.write_string(clock.to_string())
buf.write_string("kHz")
}
None => ()
}
buf.to_string()
}
///|
pub fn modes_to_text(modes : Array[DisplayMode]) -> String {
let buf = StringBuilder(size_hint=modes.length() * 48)
let mut first = true
for mode in modes {
if first {
first = false
} else {
buf.write_char('\n')
}
buf.write_string("- ")
buf.write_string(mode_to_text(mode))
}
buf.to_string()
}
///|
fn append_detailed_modes(modes : Array[DisplayMode], edid : Edid) -> Unit {
let mut seen_detailed = false
for descriptor in edid.descriptors {
match descriptor.detailed_timing {
Some(timing) => {
modes.push(mode_from_detailed(timing, !seen_detailed))
seen_detailed = true
}
None => ()
}
}
}
///|
fn append_standard_modes(modes : Array[DisplayMode], edid : Edid) -> Unit {
for timing in edid.standard_timings {
if timing.valid {
modes.push(mode_from_standard(timing))
}
}
}
///|
fn append_established_modes(modes : Array[DisplayMode], edid : Edid) -> Unit {
for timing in edid.established_timings {
if timing.enabled {
modes.push(mode_from_established(timing))
}
}
}
///|
fn mode_from_detailed(
timing : DetailedTimingInfo,
preferred : Bool,
) -> DisplayMode {
{
width: timing.h_active,
height: timing.v_active,
refresh_millihz: timing.refresh_millihz(),
pixel_clock_khz: Some(timing.pixel_clock_khz),
source: if preferred {
PreferredDetailed
} else {
DetailedDescriptor
},
interlaced: timing.interlaced,
preferred,
name: timing.resolution(),
}
}
///|
fn mode_from_standard(timing : StandardTiming) -> DisplayMode {
{
width: timing.width,
height: timing.height,
refresh_millihz: timing.refresh_hz * 1000,
pixel_clock_khz: None,
source: StandardTimingSource,
interlaced: false,
preferred: false,
name: timing.width.to_string() + "x" + timing.height.to_string(),
}
}
///|
fn mode_from_established(timing : EstablishedTiming) -> DisplayMode {
{
width: timing.width,
height: timing.height,
refresh_millihz: timing.refresh_hz * 1000,
pixel_clock_khz: None,
source: EstablishedTimingSource,
interlaced: timing.name.contains("i"),
preferred: false,
name: timing.name,
}
}
///|
fn unique_modes(modes : Array[DisplayMode]) -> Array[DisplayMode] {
let out : Array[DisplayMode] = []
for mode in modes {
if !contains_similar_mode(out, mode) {
out.push(mode)
}
}
out
}
///|
fn contains_similar_mode(
modes : Array[DisplayMode],
mode : DisplayMode,
) -> Bool {
for existing in modes {
if existing.width == mode.width &&
existing.height == mode.height &&
refresh_close(existing.refresh_millihz, mode.refresh_millihz) {
return true
}
}
false
}
///|
fn refresh_close(left : Int, right : Int) -> Bool {
let diff = if left > right { left - right } else { right - left }
diff <= 1000
}
///|
fn mode_score(mode : DisplayMode) -> Int {
let base = mode.pixel_count()
let refresh_bonus = mode.refresh_hz_rounded() * 100
let preferred_bonus = if mode.preferred { 10000000 } else { 0 }
let interlace_penalty = if mode.interlaced { 1000000 } else { 0 }
base + refresh_bonus + preferred_bonus - interlace_penalty
}
///|
pub fn dmt_name_for_mode(mode : DisplayMode) -> String? {
let table = known_dmt_modes()
for known in table {
if known.width == mode.width &&
known.height == mode.height &&
refresh_close(known.refresh_millihz, mode.refresh_millihz) {
return Some(known.name)
}
}
None
}
///|
pub fn known_dmt_modes() -> Array[DisplayMode] {
[
known_mode(640, 350, 85000, "DMT 640x350@85"),
known_mode(640, 400, 85000, "DMT 640x400@85"),
known_mode(720, 400, 70000, "DMT 720x400@70"),
known_mode(720, 400, 85000, "DMT 720x400@85"),
known_mode(640, 480, 60000, "DMT 640x480@60"),
known_mode(640, 480, 72000, "DMT 640x480@72"),
known_mode(640, 480, 75000, "DMT 640x480@75"),
known_mode(640, 480, 85000, "DMT 640x480@85"),
known_mode(800, 600, 56000, "DMT 800x600@56"),
known_mode(800, 600, 60000, "DMT 800x600@60"),
known_mode(800, 600, 72000, "DMT 800x600@72"),
known_mode(800, 600, 75000, "DMT 800x600@75"),
known_mode(800, 600, 85000, "DMT 800x600@85"),
known_mode(848, 480, 60000, "DMT 848x480@60"),
known_mode(1024, 768, 87000, "DMT 1024x768@87i"),
known_mode(1024, 768, 60000, "DMT 1024x768@60"),
known_mode(1024, 768, 70000, "DMT 1024x768@70"),
known_mode(1024, 768, 75000, "DMT 1024x768@75"),
known_mode(1024, 768, 85000, "DMT 1024x768@85"),
known_mode(1152, 864, 75000, "DMT 1152x864@75"),
known_mode(1280, 768, 60000, "DMT 1280x768@60"),
known_mode(1280, 768, 75000, "DMT 1280x768@75"),
known_mode(1280, 768, 85000, "DMT 1280x768@85"),
known_mode(1280, 800, 60000, "DMT 1280x800@60"),
known_mode(1280, 800, 75000, "DMT 1280x800@75"),
known_mode(1280, 800, 85000, "DMT 1280x800@85"),
known_mode(1280, 960, 60000, "DMT 1280x960@60"),
known_mode(1280, 960, 85000, "DMT 1280x960@85"),
known_mode(1280, 1024, 60000, "DMT 1280x1024@60"),
known_mode(1280, 1024, 75000, "DMT 1280x1024@75"),
known_mode(1280, 1024, 85000, "DMT 1280x1024@85"),
known_mode(1360, 768, 60000, "DMT 1360x768@60"),
known_mode(1366, 768, 60000, "DMT 1366x768@60"),
known_mode(1400, 1050, 60000, "DMT 1400x1050@60"),
known_mode(1400, 1050, 75000, "DMT 1400x1050@75"),
known_mode(1400, 1050, 85000, "DMT 1400x1050@85"),
known_mode(1440, 900, 60000, "DMT 1440x900@60"),
known_mode(1440, 900, 75000, "DMT 1440x900@75"),
known_mode(1440, 900, 85000, "DMT 1440x900@85"),
known_mode(1600, 900, 60000, "DMT 1600x900@60"),
known_mode(1600, 1200, 60000, "DMT 1600x1200@60"),
known_mode(1600, 1200, 65000, "DMT 1600x1200@65"),
known_mode(1600, 1200, 70000, "DMT 1600x1200@70"),
known_mode(1600, 1200, 75000, "DMT 1600x1200@75"),
known_mode(1600, 1200, 85000, "DMT 1600x1200@85"),
known_mode(1680, 1050, 60000, "DMT 1680x1050@60"),
known_mode(1680, 1050, 75000, "DMT 1680x1050@75"),
known_mode(1680, 1050, 85000, "DMT 1680x1050@85"),
known_mode(1792, 1344, 60000, "DMT 1792x1344@60"),
known_mode(1792, 1344, 75000, "DMT 1792x1344@75"),
known_mode(1856, 1392, 60000, "DMT 1856x1392@60"),
known_mode(1856, 1392, 75000, "DMT 1856x1392@75"),
known_mode(1920, 1080, 60000, "DMT 1920x1080@60"),
known_mode(1920, 1200, 60000, "DMT 1920x1200@60"),
known_mode(1920, 1200, 85000, "DMT 1920x1200@85"),
known_mode(1920, 1440, 60000, "DMT 1920x1440@60"),
known_mode(1920, 1440, 75000, "DMT 1920x1440@75"),
known_mode(2048, 1152, 60000, "DMT 2048x1152@60"),
known_mode(2560, 1440, 60000, "DMT 2560x1440@60"),
known_mode(2560, 1600, 60000, "DMT 2560x1600@60"),
known_mode(3840, 2160, 30000, "CTA 3840x2160@30"),
known_mode(3840, 2160, 60000, "CTA 3840x2160@60"),
known_mode(4096, 2160, 60000, "CTA 4096x2160@60"),
]
}
///|
fn known_mode(
width : Int,
height : Int,
refresh_millihz : Int,
name : String,
) -> DisplayMode {
{
width,
height,
refresh_millihz,
pixel_clock_khz: None,
source: DmtDatabase,
interlaced: name.contains("i"),
preferred: false,
name,
}
}