Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to create multiple windows in multiple monitors? #7600

Open
lewiszlw opened this issue Feb 10, 2023 · 21 comments
Open

How to create multiple windows in multiple monitors? #7600

lewiszlw opened this issue Feb 10, 2023 · 21 comments
Labels
A-Windowing Platform-agnostic interface layer to run your app in C-Bug An unexpected or incorrect behavior O-MacOS Specific to the MacOS (Apple) desktop operating system S-Needs-Investigation This issue requires detective work to figure out what's going wrong

Comments

@lewiszlw
Copy link
Member

Bevy version

main branch (lastest commit is cd447fb4e68716fb908158ab9ca64d13746a6b97).

[Optional] Relevant system information

AdapterInfo { name: "Apple M1", vendor: 0, device: 0, device_type: IntegratedGpu, driver: "", driver_info: "", backend: Metal }

SystemInfo { os: "MacOS 13.0 ", kernel: "22.1.0", cpu: "Apple M1", core_count: "8", memory: "16.0 GiB" }

[DisplayInfo { id: 1, x: 0, y: 0, width: 1440, height: 900, rotation: 0.0, scale_factor: 2.0, is_primary: true }, DisplayInfo { id: 2, x: -264, y: -1080, width: 1920, height: 1080, rotation: 0.0, scale_factor: 1.0, is_primary: false }]

What you did

I'm trying to run offical example multiple_windows and want second window putted on second monitor. I tried

    let second_window = commands
        .spawn(Window {
            title: "Second window".to_owned(),
            position: WindowPosition::At(IVec2::new(-264, -1080)),
            resolution: WindowResolution::new(1920., 1080.),
            ..default()
        })
        .id();

and

    let second_window = commands
        .spawn(Window {
            title: "Second window".to_owned(),
            position: WindowPosition::Centered(MonitorSelection::Index(1)),   // I tried 0,1,2,3 here
            resolution: WindowResolution::new(1920., 1080.),
            ..default()
        })
        .id();

What went wrong

The second window is always on my primary monitor.

@lewiszlw lewiszlw added C-Bug An unexpected or incorrect behavior S-Needs-Triage This issue needs to be labelled labels Feb 10, 2023
@alice-i-cecile alice-i-cecile added A-Windowing Platform-agnostic interface layer to run your app in and removed S-Needs-Triage This issue needs to be labelled labels Feb 10, 2023
@lewiszlw
Copy link
Member Author

Hi, @Aceeri , could you give a look at this? I can put second window on second monitor when using bevy 0.9.1. I'm not sure if windows as entities pr breaks this or something I missed.

@Aceeri
Copy link
Member

Aceeri commented Feb 16, 2023

I'll double check that this works, never really tested it specifically myself just took existing code.

@Aceeri
Copy link
Member

Aceeri commented Feb 16, 2023

Hmm, it seems to work fine on Windows. I do have a M1 laptop to try and test this on, but I'll try doing that tomorrow.

@Aceeri
Copy link
Member

Aceeri commented Feb 16, 2023

@lewiszlw Would you mind posting the logs from running the code with

position: WindowPosition::Centered(MonitorSelection::Index(1)),

?

Just wondering if there is maybe a WARN about it not finding the second monitor.

@lewiszlw
Copy link
Member Author

2023-02-16T08:38:20.479995Z  INFO bevy_render::renderer: AdapterInfo { name: "Apple M1", vendor: 0, device: 0, device_type: IntegratedGpu, driver: "", driver_info: "", backend: Metal }
2023-02-16T08:38:20.905247Z  INFO bevy_winit::system: Creating new window "Bevy App" (0v0)
2023-02-16T08:38:21.086569Z  INFO bevy_diagnostic::system_information_diagnostics_plugin::internal: SystemInfo { os: "MacOS 13.0 ", kernel: "22.1.0", cpu: "Apple M1", core_count: "8", memory: "16.0 GiB" }
2023-02-16T08:38:21.212242Z  INFO bevy_winit::system: Creating new window "Second window" (4v0)
2023-02-16T08:38:27.260902Z  INFO bevy_winit::system: Closing window 0v0
2023-02-16T08:38:27.316784Z  WARN bevy_winit: Window 0v0 is missing `Window` component, skipping event Destroyed
2023-02-16T08:38:28.674905Z  INFO bevy_window::system: No windows are open, exiting
2023-02-16T08:38:28.677308Z  INFO bevy_winit::system: Closing window 4v0

@SpecificProtagonist
Copy link
Contributor

Works for me on Linux (X11) too.

@Aceeri
Copy link
Member

Aceeri commented Feb 16, 2023

@lewiszlw Would you be open to debugging this further on your end? Can ask me any questions about the windowing code in general.

Could also do it live somewhere if you think that'd be helpful.

@lewiszlw
Copy link
Member Author

Sure, I'll try a deep dive.

@lewiszlw
Copy link
Member Author

I have some findings @Aceeri

  1. When I put primary window on primary monitor, if I set a negative number (ex. -200) to Y of position of the second window, the second window will be created on bottom of my primary monitor rather than second monitor (seems the second monitor wasn't detected).
  2. When I put primary window on second monitor, if I set a negative number to Y of position of the second window, the second window will be created on second monitor as expected.

Seems the process behind creating primary window on second monitor will init somthing for the second monitor.

This is my monitors arrangement.
image

@Aceeri
Copy link
Member

Aceeri commented Feb 17, 2023

Hmm, this makes me think we either are or aren't flipping the coordinate space for position in the y direction.

Either way our vertical coordinate space is differing from winits I think.

@lewiszlw
Copy link
Member Author

I tried winit multiwindow example, everything works fine.

@lewiszlw
Copy link
Member Author

There are some talk in #6526, maybe helpful.

@Aceeri
Copy link
Member

Aceeri commented Feb 27, 2023

Can't seem to repro this still, tried moving my monitors around to be in a similar set up but it seems to work fine hmm

@honungsburk
Copy link
Contributor

I've run into the same problem on bevy 0.12.0

SystemInfo { os: "MacOS 14.1.1 ", kernel: "23.1.0", cpu: "Apple M1 Pro", core_count: "8", memory: "16.0 GiB" }

AdapterInfo { name: "Apple M1 Pro", vendor: 0, device: 0, device_type: IntegratedGpu, driver: "", driver_info: "", backend: Metal }

@DrewRidley
Copy link

DrewRidley commented Jan 29, 2024

@lewiszlw, how were you able to get the primary window to be constructed on a secondary monitor?

        .add_plugins(DefaultPlugins.set(WindowPlugin {
            primary_window: Some(Window {
                position: WindowPosition::Centered(MonitorSelection::Index(1)),
                ..Default::default()
            }),
            ..Default::default()
        }))

always results in the window being on the primary display, no matter which index is used.
This only happens on macOS.

@lewiszlw
Copy link
Member Author

I think the method is like you pasted, setting position field.

@TarekAS
Copy link

TarekAS commented Aug 13, 2024

@DrewRidley Did you manage to get the window to show up on the second monitor?

This configuration is still not working on M1 Mac:

.set(WindowPlugin {
    primary_window: Some(Window {
        mode: bevy::window::WindowMode::Fullscreen,
        resizable: false,
        position: WindowPosition::Centered(MonitorSelection::Index(1)),
        ..default()
    }),
    ..default()
}),

No matter what the value of the index, the window only shows up on the primary display.

@RockyGitHub
Copy link

RockyGitHub commented Nov 6, 2024

This is also an issue for me. M3 Macbook
It seems to be dependent on what terminal is active, in which monitor, when compilation finishes/program runs

Like, I may be editing on terminal 0 on monitor 0.
Then I could have cargo watch -x run running on terminal 1, on monitor 1.

The game opens up on monitor 0.
If I click on terminal 1 before the compilation finishes, the game will open on monitor 1

@BenjaminBrienen BenjaminBrienen added O-MacOS Specific to the MacOS (Apple) desktop operating system S-Needs-Investigation This issue requires detective work to figure out what's going wrong labels Nov 7, 2024
@arahisman
Copy link

arahisman commented Nov 8, 2024

@RockyGitHub
I just solved it for myself in macOS with current main git branch of bevy by using core-graphics = "0.23.1"
All you need is get list of all your displays

pub fn get_active_displays() -> Result<Vec<CGDirectDisplayID>, String> {
    unsafe {
        let max_displays = 16; // Максимальное количество дисплеев для поиска
        let mut displays: Vec<CGDirectDisplayID> = vec![0; max_displays];
        let mut display_count: u32 = 0;

        let result = CGGetActiveDisplayList(
            max_displays as u32,
            displays.as_mut_ptr(),
            &mut display_count,
        );

        if result != 0 {
            return Err("Failed to get active displays".to_string());
        }

        displays.truncate(display_count as usize);
        Ok(displays)
    }
}

Then you can iterate the vector of display ids and call

fn get_display_size(display_id: CGDirectDisplayID) -> Result<(u32, u32), String> {
    unsafe {
        let width = CGDisplayPixelsWide(display_id);
        let height = CGDisplayPixelsHigh(display_id);

        if width == 0 || height == 0 {
            return Err("Failed to get display dimensions".to_string());
        }

        Ok((width as u32, height as u32))
    }
}

Now all you need is iterate the current list of monitors in monitor selection and check if any window

pub fn monitor_select_system(
    mut commands: Commands,
    monitors: Query<(Entity, &Monitor)>,
) {
       let display_list = get_active_displays();
       let id_of_display_i_need = //here you can find the display you want to contain new window. You can do this by checking the display size with function get_display_size, or any other way you want. For example, CGDisplay contains such functions as isBuiltin isMain and many other. Just don't forget to check the results of functions with is_some() and unwrap the results to get the real value.  
       let display_i_need = CGDisplay::new(id_of_display_i_need)
      //Now lets find the monitor entity
      let monitor_entity = monitors
            .iter()
            .find(|(_, monitor)| {
                monitor.physical_height as u64 == display_i_need.pixels_high()
                    && monitor.physical_width as u64 == display_i_need.pixels_wide()
            })
            .map(|(entity, _)| entity);
      //Check if entity was successfully found and create a new window (Primary in this example)
      if monitor_entity.is_some() { 
           commands.spawn((
               Window {
                   present_mode: PresentMode::Fifo,
                   mode: WindowMode::Fullscreen(MonitorSelection::Entity(monitor_entity.unwrap()));
                   ..default()
              },
              PrimaryWindow
            )).id();

       //Or if you want the window to be in windowed mode just set its position like this
           commands.spawn((
               Window {
                   present_mode: PresentMode::Fifo,
                   mode: WindowMode::Windowed,
                   position: WindowPosition::Centered(MonitorSelection::Entity(monitor_entity.unwrap()));
                   ..default()
              },
              PrimaryWindow
            )).id();
      }
}

Hope it will help someone

@RockyGitHub
Copy link

Hi @arahisman , this looks very promising but I can't seem to find where the Monitor struct in the monitor_select_system() query is supposed to be defined?

@arahisman
Copy link

Hi @arahisman , this looks very promising but I can't seem to find where the Monitor struct in the monitor_select_system() query is supposed to be defined?

Hi! This functionality was added in the new version of bevy 0.15.x. Here's a code example from my application for ar-glasses:

pub fn update_primary_window_position(
    mut commands: Commands,
    monitors_added: Query<(Entity, &Monitor)>,
    display_manager: ResMut<DisplayManager>,
    mut windows: Query<(Entity, &mut Window)>,
    mut window_state: ResMut<WindowState>,
) {
    if let Some(glasses_display_id) = display_manager.glasses_display {
        let glasses_display = CGDisplay::new(glasses_display_id);
        let glasses_monitor_entity = monitors_added
            .iter()
            .find(|(_, monitor)| {
                monitor.physical_height as u64 == glasses_display.pixels_high()
                    && monitor.physical_width as u64 == glasses_display.pixels_wide()
            })
            .map(|(entity, _)| entity);
        let glasses_monitor = monitors_added
            .iter()
            .find(|(_, monitor)| {
                monitor.physical_height as u64 == glasses_display.pixels_high()
                    && monitor.physical_width as u64 == glasses_display.pixels_wide()
            })
            .map(|(_, monitor)| monitor);

        let primary_window_entity = window_state.primary;
        if let Ok((_, mut window)) = windows.get_mut(primary_window_entity) {
            if glasses_monitor_entity.is_some() {
                if window.position
                    != WindowPosition::Centered(MonitorSelection::Entity(
                        glasses_monitor_entity.unwrap(),
                    ))
                {
                    println!(
                        "glasses monitor position {:?} primary window position {:?}",
                        glasses_monitor.unwrap().physical_position,
                        window.position
                    );
                    window.position = WindowPosition::Centered(MonitorSelection::Entity(
                        glasses_monitor_entity.unwrap(),
                    ));
                    window.mode =
                             WindowMode::BorderlessFullscreen(MonitorSelection::Entity(glasses_monitor_entity.unwrap()));
                    window.decorations = false;
                    window.transparent = true;
                    window.composite_alpha_mode = CompositeAlphaMode::Auto;
                }
            } else {
            }
        }
    }
}

WindowState is the resource that contains a window entity

#[derive(Resource)]
struct WindowState {
    primary: Entity,
    secondary: Vec<Entity>,
}

fn setup:

 let primary_window = commands
        .spawn((
            Window {
                composite_alpha_mode: CompositeAlphaMode::Auto,
                present_mode: PresentMode::Fifo,
                mode: WindowMode::Windowed,
                ..default()
            },
            PrimaryWindow,
        ))
        .id();
    commands.insert_resource(WindowState::new(primary_window));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Windowing Platform-agnostic interface layer to run your app in C-Bug An unexpected or incorrect behavior O-MacOS Specific to the MacOS (Apple) desktop operating system S-Needs-Investigation This issue requires detective work to figure out what's going wrong
Projects
None yet
Development

No branches or pull requests

10 participants