shoes / shoes3

a tiny graphical app kit for ruby
http://walkabout.mvmanila.com
Other
181 stars 19 forks source link

Multiple monitors feature #374

Closed IanTrudel closed 5 years ago

IanTrudel commented 7 years ago

This is the proposed API for multiple monitors support on Shoes. A follow-up on issue #64 for which it now requires its own issue. Please, take a look at the API and give your feedback here.

Sample code:

Shoes.app(width: 250, height: 150) do
    para "There are #{app.monitors + 1} monitors available."
end
Shoes.app(width: 250, height: 150, monitor: 0) do
    para "this window is displayed on your first monitor"
    window(width: 250, height: 150, monitor: 1) do
        para "this window is displayed on your second monitor"
    end
end
Shoes.app(width: 250, height: 150) do
    button("move to next monitor") do
        while ((n = rand(app.monitors)) == app.monitor) do; end
        app.monitor = n
    end
end
ccoupe commented 7 years ago

Since I know nothing I started at the top level. How does a osx user work with multiple monitors. Many issues arise. Extended desktop is the only configuration we care about. Note that they say an app's windows open on the primary and the user drags the window to the other monitor. If that's it, Shoes is mostly compatible now.

If we want to explicitly set the monitor for a shoes window we do this in osx in slightly modified shoes_native_app_window. Seems easy. Dialogs do not have that ability. OSX has rules about that.

This article for pygtk is a reasonable starting point no easy answer for the question of what the new top,left should be - it will be wrong for someone.

My only question? about the api is one would expect app.monitors to return 1 - not zero based. We can subtract -1 in shoes_native_window()

ccoupe commented 7 years ago

I also found something think harder about Mouse position.

IanTrudel commented 7 years ago

Valuable feedback.

RE: extended desktop

That would be correct. I believe Windows has a similar behaviour. What happens when Shoes is opened from a terminal on a second monitor? I kinda recall that would open on the same monitor rather than primary.

On FreeBSD, it normally opens on the monitor where the mouse pointer is at the time of rendering.

Alternatively, it could open on the designated monitor and fallback to the primary monitor if the designated monitor is not found.

RE: top, left

Alternatively, we could let the OS decide as it is the default behaviour for Shoes. If top, left and monitor are passed to Shoes.app hash parameters, then the user should know where to place it properly anyway... we just let them do it.

My only question? about the api is one would expect app.monitors to return 1 - not zero based. We can subtract -1 in shoes_native_window()

I'm tempted to agree with you. The reason it was 0 is that most APIs, such as GTK, designate the primary monitor as zero. But we could settle for 1 as it makes more sense to users.

ccoupe commented 7 years ago

If top, left and monitor are passed to Shoes.app

As in Shoes.app {width: 400, height: 600, monitor: 2, top: 40, left: 20} do .. end ?

These should be stored in the shoes_app (see app.h) structure which is passed in to shoes_native_window()

I've created a branch 'monitors' at my github origin (your upstream) hopefully you can git pull upstream monitors - nothing new yet but it's the playground.

IanTrudel commented 7 years ago

As in Shoes.app {width: 400, height: 600, monitor: 2, top: 40, left: 20} do .. end ?

Correct.

These should be stored in the shoes_app (see app.h) structure which is passed in to shoes_native_window()

Yes, nothing fancy. Just like top, left, width, height, etc.

I've created a branch 'monitors' at my github origin (your upstream) hopefully you can git pull upstream monitors - nothing new yet but it's the playground.

I probably should resync with the whole Shoes3 at this point anyway.

ccoupe commented 7 years ago

One other thing, app->monitors == 0 means { monitor: n } was not provided - Shoes as usual. top: and left: should be parsed and stored by shoes_app_window() but for now, only shoes_native_window() needs to know that something special needs to be done for app->monitor > 0. Purists might want another field in shoes_app - 'use_monitors' or something like that. so the monitors field is not using hidden value assumptions.

IanTrudel commented 7 years ago

Yes, we always default to primary monitor. It will ensure compatibility.

Purists might want another field in shoes_app - 'have_monitors' or something like that.

Sorry? I didn't get what you mean here.

ccoupe commented 7 years ago

Sorry? I didn't get what you mean here

It's Ruby vs C thing style thing. Is app->monitors a boolean or is it a number with special meanings depending on the value like a C union (shudder) ? At the moment, I think we need that boolean app->use_monitors so its apparent that different processing maybe needed (thinking ahead about the mouse issue)

IanTrudel commented 7 years ago

Neither. A call to app.monitors should always call a (gtk, macOS) function to dynamically check the number of monitors. Occasionally, a monitor is disconnected and no longer available. We need to be avare of that and thus always check. For example, let's say someone is running a Shoes app and then plug a projector for an audience.

shoes_app structure will (or may?) contain a member monitor (int) to pass monitor information down to whatever needs it, if necessary. We still need to keep track at runtime though, as a user could move one window to one monitor to another.

Basically, any data is volatile, it will be best to check at runtime each time it is relevant. Shouldn't be a big deal though.

ccoupe commented 7 years ago

We still need to keep track at runtime though, as a user could move one window to one monitor to another.

Shoes has no way to learn that the user moved the window to another monitor except by asking where the window is when a shoes script wants to know and that may not be accurate at all times. Perhaps an 'app.monitor` method like you suggested?

ccoupe commented 7 years ago

For example, let's say someone is running a Shoes app and then plug a projector for an audience.

How would we know? A very good example to think about. That would be mirrored and not extended in osx terms.

IanTrudel commented 7 years ago

We don't need to know anything. That's the beauty of it.

Listen, this is very simple.

  1. We need to call the system API (GTK/macOS) before creating a window to know which monitor.
  2. We call the system API when a Shoes user call app.monitors or app.monitor instead of stored values in shoes_app structure.
  3. Always read system API values for the number of monitors when setting a new monitor (either Shoes app hash parameters or app.monitor=).

So what happens when one disconnect a monitor or projector? Nothing.

However, when a Shoes user creates a new window or use app.monitor, app.monitor=, we do call the system API first and check against whatever the user input. So, let's say a Shoes.app has a button that creates a window on monitor 2 but the monitor has been disconnected, Shoes internal will check the list of monitors and understand there is no longer a monitor 2 and will display the window on monitor 0.

Is this making more sense to you? We always check number of monitors in app.c:shoes_app_window (and app.monitor= C code) before setting anything. app.monitors and app.monitor will be wrappers to system calls instead of using shoes_app.

ccoupe commented 6 years ago

After more thought, in Shoes 3.3.7, some of this information belongs in the shoes_world_t struct, along with some other 'new' variables. and some other mis-uses of the app (like image cache clearing). I've ordered a new video card that should let my Linux box handle multiple monitors - I have a spare 19" 4:3 monitor that will be a good test.

dredknight commented 6 years ago

Welcome back!

ccoupe commented 6 years ago

Tests/gapp/app1.rb: on OSX: settings

Now I need a willing OSX user with a multiple monitor setup to test further.

kantel commented 6 years ago

Running the Script 1 with Shoes 3.3.7 beta on MacOS X 10.10.5 (Yosemite) with two monitors:

test1

kantel commented 6 years ago

Running the Script 2 with Shoes 3.3.7 beta on MacOS X 10.10.5 (Yosemite) with two monitors:

test2

Move this app moves the main window to the second monitor but Second monitor opens a window always at the same monitor where the main window is.

ccoupe commented 6 years ago

Thanks @kantel ! I think I see my bug. I was not setting the rect.origin to the second monitor. Would you please try the latest beta (Apr 11, 13:42) ?

ccoupe commented 6 years ago

For documentation purposes. I've got a Linux system with two monitors now. Here's what Shoes thinks it has. linux-multimon

It has different problems from OSX when displaying on the second monitor from Shoes but thats why we test.

kantel commented 6 years ago

I only found build 3.3.7 r3194 (11. April 2018, 12:42) at the usual place

test3 and with this beta I've got the same behaviour as yesterday:

with one difference: The default monitor is 1

test4

But this is a normal behaviour -- it depends which monitor the focus has. I can reproduces this.

ccoupe commented 6 years ago

Correct, the default monitor can be shifted by the user and the script is a little dumb about that.. I discovered yesterdays that my mac mini has a display port so I order an inexpensive dongle to convert to vga/dvd/hdmi. I should be able to test the Mac code in a day or two.

ccoupe commented 6 years ago

This is a better test script. Uses all the Shoes monitor methods - working on Linux, Windows, and Freebsd. Behaves better default.

Shoes.app do 
  stack do
    para "Muliple Monitor Test"
    st = Shoes.settings
    @eb = edit_box width: 400, height: 150
  end
  start do
    st = Shoes.settings
    dflt = st.monitor_default
    @eb.append "Default monitor is #{dflt}\n"
    st.monitor_count.times do |mon|
      @eb.append "Monitor #{mon} => #{st.monitor_geometry(mon)}\n"
    end
    (st.monitor_count- 1).times do |mon|
       flow do 
        newmon = (dflt ^ 1)
        button "New Window on #{newmon}" do
          window title: "Launched in #{newmon}", monitor: newmon do
            stack do
              para "My Monitor is #{app.monitor}"
              button "Dialog" do
                alert "Which Windows?"
              end
            end
          end
        end
        button "Move This to #{newmon}" do
          app.monitor = newmon
        end
        button "Restore this to #{dflt}" do
         app.monitor = dflt
        end
      end
    end
  end
end
kantel commented 6 years ago

I tried this script on my Mac with Shoes build 3.3.7 r3194, too. It runs and New Window on 1 and Restore this to 0 are Working, but New Window on 1 opens the window always on the monitor where the main window is (in my case Monitor 0) but it says, it is on Monitor 1 (see screenshot):

monitortest05

ccoupe commented 6 years ago

My osx dongle is working. The move to buttons work but the new window doesn't. I suspect screen vs view co-ordinates. monitor

ccoupe commented 6 years ago

@kantel - Well, that took a while! I've got another beta for osx that works for me. My setup has the second monitor to the right of main monitor which maybe different that yours.

kantel commented 6 years ago

It works. In my setting the monitor 0 is on the left and the monitor 1 is on the right. But it doesn't matter. Either if I declare the monitor 0 or the monitor 1 to the default monitor, the test programms work as expected (see screenshots):

default0

default1

The windows moved correctly to 0 or 1. (Pls. excuse my bad (d)english.) But I think, everything is okay.

ccoupe commented 6 years ago

Need a full screen test app:

Shoes.app do 
  stack do
    para "Muliple Monitor Full Screen Test"
    st = Shoes.settings
    @eb = edit_box width: 400, height: 150
  end
  start do
    st = Shoes.settings
    dflt = st.monitor_default
    @eb.append "Default monitor is #{dflt}\n"
    st.monitor_count.times do |mon|
      @eb.append "Monitor #{mon} => #{st.monitor_geometry(mon)}\n"
    end
    button "Tooggle FullScreen #{dflt}" do
      state = app.fullscreen 
      app.fullscreen = (state ? false : true);
    end
    (st.monitor_count - 1).times do |mon|
       flow do 
        newmon = (dflt ^ 1)
        button "New Window on #{newmon}" do
          window title: "Launched in #{newmon}", monitor: newmon, fullscreen: false do
            stack do
              para "My Monitor is #{app.monitor}"
              button "Fullscreen #{app.monitor}" do
                state = app.fullscreen
                app.fullscreen = (state ? false : true);
              end
              @eb = edit_box width: 300, height: 150
            end
          end
        end
      end
    end
  end
end

Almost works correctly on OSX.

kantel commented 6 years ago

It works as expected. I was able to tooggle a full screen on monitor 0 and on monitor 1 and it also opens a new window at 1 which I could tooggle, too.

bildschirmfoto 2018-04-23 um 13 07 08

ccoupe commented 5 years ago

Closing 3.3.7 issues.