diff --git a/src/main.rs b/src/main.rs
index e10b9d52fb0573e00fb12f1e2d8650789c4e9e61..70e8e6b249d74ca59a8e84d785b9d8b2ea55172f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -103,9 +103,9 @@ pub struct KernelArgs {
 fn select_mode<
     D: Disk,
     V: Iterator<Item=OsVideoMode>
->(os: &mut dyn Os<D, V>) -> Option<OsVideoMode> {
+>(os: &mut dyn Os<D, V>, output: usize) -> Option<OsVideoMode> {
     let mut modes = Vec::new();
-    for mode in os.video_modes() {
+    for mode in os.video_modes(output) {
         let mut aspect_w = mode.width;
         let mut aspect_h = mode.height;
         for i in 2..cmp::min(aspect_w / 2, aspect_h / 2) {
@@ -130,7 +130,7 @@ fn select_mode<
 
     // Set selected based on best resolution
     let mut selected = modes.get(0).map_or(0, |x| x.0.id);
-    if let Some((best_width, best_height)) = os.best_resolution() {
+    if let Some((best_width, best_height)) = os.best_resolution(output) {
         println!("Best resolution: {}x{}", best_width, best_height);
         for (mode, _text) in modes.iter() {
             if mode.width == best_width && mode.height == best_height {
@@ -375,6 +375,19 @@ fn main<
 >(os: &mut dyn Os<D, V>) -> (usize, u64, KernelArgs) {
     println!("Redox OS Bootloader {} on {}", env!("CARGO_PKG_VERSION"), os.name());
 
+    /*TODO: support multiple outputs
+    for output_i in 0..os.video_outputs() {
+        print!("{}:", output_i);
+        if let Some(best) = os.best_resolution(output_i) {
+            print!(" *{}x{}*", best.0, best.1);
+        }
+        for mode in os.video_modes(output_i) {
+            print!(" {}x{}", mode.width, mode.height);
+        }
+        println!();
+    }
+    */
+
     let (mut fs, password_opt) = redoxfs(os);
 
     print!("RedoxFS ");
@@ -387,7 +400,7 @@ fn main<
     }
     println!(": {} MiB", fs.header.size() / MIBI as u64);
 
-    let mode_opt = select_mode(os);
+    let mode_opt = select_mode(os, 0);
 
     let stack_size = 128 * KIBI;
     let stack_base = os.alloc_zeroed_page_aligned(stack_size);
@@ -503,7 +516,7 @@ fn main<
 
         if let Some(mut mode) = mode_opt {
             // Set mode to get updated values
-            os.set_video_mode(&mut mode);
+            os.set_video_mode(0, &mut mode);
 
             let virt = unsafe {
                 paging_framebuffer(
diff --git a/src/os/bios/mod.rs b/src/os/bios/mod.rs
index 9c98e94613f32e1fa5d56e4dc3eb59c2b11904eb..3e9f1a33f4576fd0e5fa8fbae1598ce5493d25d1 100644
--- a/src/os/bios/mod.rs
+++ b/src/os/bios/mod.rs
@@ -90,11 +90,16 @@ impl Os<
         redoxfs::FileSystem::open(disk, password_opt, Some(block), false)
     }
 
-    fn video_modes(&self) -> VideoModeIter {
+    fn video_outputs(&self) -> usize {
+        //TODO: return 1 only if vbe supported?
+        1
+    }
+
+    fn video_modes(&self, _output_i: usize) -> VideoModeIter {
         VideoModeIter::new(self.thunk10)
     }
 
-    fn set_video_mode(&self, mode: &mut OsVideoMode) {
+    fn set_video_mode(&self, _output_i: usize, mode: &mut OsVideoMode) {
         // Set video mode
         let mut data = ThunkData::new();
         data.eax = 0x4F02;
@@ -103,7 +108,7 @@ impl Os<
         //TODO: check result
     }
 
-    fn best_resolution(&self) -> Option<(u32, u32)> {
+    fn best_resolution(&self, _output_i: usize) -> Option<(u32, u32)> {
         let mut data = ThunkData::new();
         data.eax = 0x4F15;
         data.ebx = 0x01;
diff --git a/src/os/mod.rs b/src/os/mod.rs
index a88e2d8c96096210ab850ca379224f8f5c67fcc4..a528a1c32c15ad66c8a44155474d73f05f627c6f 100644
--- a/src/os/mod.rs
+++ b/src/os/mod.rs
@@ -67,9 +67,10 @@ pub trait Os<
 
     fn filesystem(&self, password_opt: Option<&[u8]>) -> syscall::Result<redoxfs::FileSystem<D>>;
 
-    fn video_modes(&self) -> V;
-    fn set_video_mode(&self, mode: &mut OsVideoMode);
-    fn best_resolution(&self) -> Option<(u32, u32)>;
+    fn video_outputs(&self) -> usize;
+    fn video_modes(&self, output_i: usize) -> V;
+    fn set_video_mode(&self, output_i: usize, mode: &mut OsVideoMode);
+    fn best_resolution(&self, output_i: usize) -> Option<(u32, u32)>;
 
     fn get_key(&self) -> OsKey;
 
diff --git a/src/os/uefi/arch/aarch64.rs b/src/os/uefi/arch/aarch64.rs
index 0f4c54ae4a90ecd55fc906c70f5073d4c2d85541..361122bc1ddae0b41cfb7d8fb53955d31acacd20 100644
--- a/src/os/uefi/arch/aarch64.rs
+++ b/src/os/uefi/arch/aarch64.rs
@@ -99,9 +99,7 @@ pub fn main() -> Result<()> {
     //TODO: support this in addition to ACPI?
     // let dtb = find_dtb()?;
 
-    let mut os = OsEfi {
-        st: std::system_table(),
-    };
+    let mut os = OsEfi::new();
 
     // Disable cursor
     let _ = (os.st.ConsoleOut.EnableCursor)(os.st.ConsoleOut, false);
diff --git a/src/os/uefi/arch/x86_64.rs b/src/os/uefi/arch/x86_64.rs
index d39aeef6f757296d2060ed65b993212a3320fa14..8b616fa5e0e400e0836b95c10bf08f8f4b5472d2 100644
--- a/src/os/uefi/arch/x86_64.rs
+++ b/src/os/uefi/arch/x86_64.rs
@@ -76,9 +76,7 @@ unsafe extern "C" fn kernel_entry(
 pub fn main() -> Result<()> {
     LOGGER.init();
 
-    let mut os = OsEfi {
-        st: std::system_table(),
-    };
+    let mut os = OsEfi::new();
 
     // Disable cursor
     let _ = (os.st.ConsoleOut.EnableCursor)(os.st.ConsoleOut, false);
diff --git a/src/os/uefi/mod.rs b/src/os/uefi/mod.rs
index 06529acd7cd29f93a7f5c7900baef92a65105028..3461862bd3285aec4b42fd16b73d9b28761a4311 100644
--- a/src/os/uefi/mod.rs
+++ b/src/os/uefi/mod.rs
@@ -1,4 +1,7 @@
+use alloc::vec::Vec;
 use core::{
+    cell::RefCell,
+    mem,
     ops::{ControlFlow, Try},
     ptr,
     slice
@@ -7,6 +10,8 @@ use std::{
     proto::Protocol,
 };
 use uefi::{
+    Handle,
+    boot::LocateSearchType,
     reset::ResetType,
     memory::MemoryType,
     status::{Result, Status},
@@ -66,6 +71,53 @@ pub(crate) fn alloc_zeroed_page_aligned(size: usize) -> *mut u8 {
 
 pub struct OsEfi {
     st: &'static SystemTable,
+    outputs: RefCell<Vec<(Output, Option<EdidActive>)>>,
+}
+
+impl OsEfi {
+    pub fn new() -> Self {
+        let st = std::system_table();
+        let mut outputs = Vec::new();
+        {
+            let guid = Output::guid();
+            let mut handles = Vec::with_capacity(256);
+            let mut len = handles.capacity() * mem::size_of::<Handle>();
+            status_to_result(
+                (st.BootServices.LocateHandle)(
+                    LocateSearchType::ByProtocol,
+                    &guid,
+                    0,
+                    &mut len,
+                    handles.as_mut_ptr()
+                )
+            ).unwrap();
+            unsafe { handles.set_len(len / mem::size_of::<Handle>()); }
+            for handle in handles {
+                //TODO: do we have to query all modes to get good edid?
+                match Output::handle_protocol(handle) {
+                    Ok(output) => {
+                        outputs.push((
+                            output,
+                            match EdidActive::handle_protocol(handle) {
+                                Ok(efi_edid) => Some(efi_edid),
+                                Err(err) => {
+                                    log::warn!("Failed to get EFI EDID from handle {:?}: {:?}", handle, err);
+                                    None
+                                }
+                            }
+                        ));
+                    },
+                    Err(err) => {
+                        log::warn!("Failed to get Output from handle {:?}: {:?}", handle, err);
+                    }
+                }
+            }
+        }
+        Self {
+            st,
+            outputs: RefCell::new(outputs),
+        }
+    }
 }
 
 impl Os<
@@ -127,12 +179,26 @@ impl Os<
         Err(syscall::Error::new(syscall::ENOENT))
     }
 
-    fn video_modes(&self) -> VideoModeIter {
-        VideoModeIter::new()
+    fn video_outputs(&self) -> usize {
+        self.outputs.borrow().len()
     }
 
-    fn set_video_mode(&self, mode: &mut OsVideoMode) {
-        let output = Output::one().unwrap();
+    fn video_modes(&self, output_i: usize) -> VideoModeIter {
+        let output_opt = match self.outputs.borrow_mut().get_mut(output_i) {
+            Some(output) => unsafe {
+                // Hack to enable clone
+                let ptr = output.0.0 as *mut _;
+                Some(Output::new(&mut *ptr))
+            },
+            None => None,
+        };
+        VideoModeIter::new(output_opt)
+    }
+
+    fn set_video_mode(&self, output_i: usize, mode: &mut OsVideoMode) {
+        //TODO: return error?
+        let mut outputs = self.outputs.borrow_mut();
+        let (output, _efi_edid_opt) = &mut outputs[output_i];
         status_to_result(
             (output.0.SetMode)(output.0, mode.id)
         ).unwrap();
@@ -143,42 +209,30 @@ impl Os<
         mode.base = output.0.Mode.FrameBufferBase as u64;
     }
 
-    fn best_resolution(&self) -> Option<(u32, u32)> {
-        //TODO: get this per output
-        match EdidActive::one() {
-            Ok(efi_edid) => {
-                let edid = unsafe {
-                    slice::from_raw_parts(efi_edid.0.Edid, efi_edid.0.SizeOfEdid as usize)
-                };
-
-                if edid.len() > 0x3D {
-                    Some((
-                        (edid[0x38] as u32) | (((edid[0x3A] as u32) & 0xF0) << 4),
-                        (edid[0x3B] as u32) | (((edid[0x3D] as u32) & 0xF0) << 4),
-                    ))
-                } else {
-                    log::warn!("EFI EDID too small: {}", edid.len());
-                    None
-                }
-            },
-            Err(err) => {
-                log::warn!("Failed to get EFI EDID: {:?}", err);
+    fn best_resolution(&self, output_i: usize) -> Option<(u32, u32)> {
+        let mut outputs = self.outputs.borrow_mut();
+        let (output, efi_edid_opt) = outputs.get_mut(output_i)?;
 
-                // Fallback to the current output resolution
-                match Output::one() {
-                    Ok(output) => {
-                        Some((
-                            output.0.Mode.Info.HorizontalResolution,
-                            output.0.Mode.Info.VerticalResolution,
-                        ))
-                    },
-                    Err(err) => {
-                        log::error!("Failed to get output: {:?}", err);
-                        None
-                    }
-                }
+        if let Some(efi_edid) = efi_edid_opt {
+            let edid = unsafe {
+                slice::from_raw_parts(efi_edid.0.Edid, efi_edid.0.SizeOfEdid as usize)
+            };
+
+            if edid.len() > 0x3D {
+                return Some((
+                    (edid[0x38] as u32) | (((edid[0x3A] as u32) & 0xF0) << 4),
+                    (edid[0x3B] as u32) | (((edid[0x3D] as u32) & 0xF0) << 4),
+                ));
+            } else {
+                log::warn!("EFI EDID too small: {}", edid.len());
             }
         }
+
+        // Fallback to the current output resolution
+        Some((
+            output.0.Mode.Info.HorizontalResolution,
+            output.0.Mode.Info.VerticalResolution,
+        ))
     }
 
     fn get_key(&self) -> OsKey {
diff --git a/src/os/uefi/video_mode.rs b/src/os/uefi/video_mode.rs
index be8dc4ea518a7b569c5bccaa4d921e0963247419..d1c7ee6d479275753d6ccff7513669ce96e818eb 100644
--- a/src/os/uefi/video_mode.rs
+++ b/src/os/uefi/video_mode.rs
@@ -11,9 +11,9 @@ pub struct VideoModeIter {
 }
 
 impl VideoModeIter {
-    pub fn new() -> Self {
+    pub fn new(output_opt: Option<Output>) -> Self {
         Self {
-            output_opt: Output::one().ok(),
+            output_opt,
             i: 0,
         }
     }