Message ID | 20230621004355.19920-7-dongwon.kim@intel.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | ui: guest displays multiple connectors suppport and hotplug in | expand |
Dongwon Kim <dongwon.kim@intel.com> writes: > From: Vivek Kasireddy <vivek.kasireddy@intel.com> > > The new parameter named "connector" can be used to assign physical > monitors/connectors to individual GFX VCs such that when the monitor > is connected or hotplugged, the associated GTK window would be > moved to it. If the monitor is disconnected or unplugged, the > associated GTK window would be hidden and a relevant disconnect > event would be sent to the Guest. > > Usage: -device virtio-gpu-pci,max_outputs=2,blob=true,... > -display gtk,gl=on,connectors.0=eDP-1,connectors.1=DP-1..... > > Cc: Gerd Hoffmann <kraxel@redhat.com> > Cc: Daniel P. Berrangé <berrange@redhat.com> > Cc: Markus Armbruster <armbru@redhat.com> > Cc: Philippe Mathieu-Daudé <philmd@linaro.org> > Cc: Marc-André Lureau <marcandre.lureau@redhat.com> > Signed-off-by: Vivek Kasireddy <vivek.kasireddy@intel.com> > Signed-off-by: Dongwon Kim <dongwon.kim@intel.com> [...] > --- a/qapi/ui.json > +++ b/qapi/ui.json > @@ -1315,13 +1315,22 @@ > # @show-menubar: Display the main window menubar. Defaults to "on". > # (Since 8.0) > # > +# @connectors: List of physical monitor/connector names where the GTK > +# windows containing the respective graphics virtual consoles > +# (VCs) are to be placed. If a mapping exists for a VC, it > +# will be moved to that specific monitor or else it would > +# not be displayed anywhere and would appear disconnected > +# to the guest. > +# (Since 8.1) Please format like # @connectors: List of physical monitor/connector names where the GTK # windows containing the respective graphics virtual consoles # (VCs) are to be placed. If a mapping exists for a VC, it will # be moved to that specific monitor or else it would not be # displayed anywhere and would appear disconnected to the guest. # (Since 8.1) to blend in with recent commit a937b6aa739 (qapi: Reformat doc comments to conform to current conventions). The meaning of @connectors is less than clear. The phrase "If a mapping exists for a VC" suggests it is a mapping of sorts. "List of physical monitor/connector names" indicates it maps to physical monitor / connector name. What does it map from? VC number? How are VCs numbered? Is it the same number we use in QOM /backend/console[NUM]? Using a list for the mapping means the mapping must be dense, e.g. I can't map #0 and #2 but not #1. Is this what we want? The sentence "If a mapping exists" confusing has a dangling else ambiguity of sorts. I can interpret it as If a mapping exists for a VC: the window will be moved to that specific monitor or else it would not be displayed anywhere and would appear ... or as If a mapping exists for a VC: the window will be moved to that specific monitor or else it would not be displayed anywhere and would appear ... I think we have three cases: 0. No mapping provided 1. Mapping provided, and the named monitor / connector exists 2. Mapping provided, and the named monitor / connector does not exist We can go from case 1 to 2 (disconnect) and vice versa (connect) at any time. Please spell out behavior for each case, and for the transitions between case 1 and 2. > +# > # Since: 2.12 > ## > { 'struct' : 'DisplayGTK', > 'data' : { '*grab-on-hover' : 'bool', > '*zoom-to-fit' : 'bool', > '*show-tabs' : 'bool', > - '*show-menubar' : 'bool' } } > + '*show-menubar' : 'bool', > + '*connectors' : ['str'] } } > > ## > # @DisplayEGLHeadless: [...]
Hi Markus, So I've worked on the description of this param. Can you check if this new version looks ok? # @connectors: List of physical monitor/connector names where the GTK # windows containing the respective graphics virtual consoles (VCs) # are to be placed. Index of the connector name in the array directly # indicates the id of the VC. # For example, with "-device gtk,connectors.0=DP-1, connectors.1=DP-2", # a physical display connected to DP-1 port will be the target monitor # for VC0 and the one on DP-2 will be the target for VC1. If there is # no connector associated with a VC, then that VC won't be placed anywhere # before the QEMU is relaunched with a proper connector name set for it. # If a connector name exists for a VC but the display cable is not plugged # in when guest is launched, the VC will be just hidden but will show up # as soon as the cable is plugged in. If a display is connected in the beginning # but later disconnected, VC will immediately be hidden and guest will detect # it as a disconnected display. This option does not force 1 to 1 mapping # between the connector and the VC, which means multiple VCs can be placed # on the same display but vice versa is not possible (a single VC duplicated # on a multiple displays) # (Since 8.1) Thanks, DW On 6/20/2023 10:51 PM, Markus Armbruster wrote: > Dongwon Kim <dongwon.kim@intel.com> writes: > >> From: Vivek Kasireddy <vivek.kasireddy@intel.com> >> >> The new parameter named "connector" can be used to assign physical >> monitors/connectors to individual GFX VCs such that when the monitor >> is connected or hotplugged, the associated GTK window would be >> moved to it. If the monitor is disconnected or unplugged, the >> associated GTK window would be hidden and a relevant disconnect >> event would be sent to the Guest. >> >> Usage: -device virtio-gpu-pci,max_outputs=2,blob=true,... >> -display gtk,gl=on,connectors.0=eDP-1,connectors.1=DP-1..... >> >> Cc: Gerd Hoffmann <kraxel@redhat.com> >> Cc: Daniel P. Berrangé <berrange@redhat.com> >> Cc: Markus Armbruster <armbru@redhat.com> >> Cc: Philippe Mathieu-Daudé <philmd@linaro.org> >> Cc: Marc-André Lureau <marcandre.lureau@redhat.com> >> Signed-off-by: Vivek Kasireddy <vivek.kasireddy@intel.com> >> Signed-off-by: Dongwon Kim <dongwon.kim@intel.com> > [...] > >> --- a/qapi/ui.json >> +++ b/qapi/ui.json >> @@ -1315,13 +1315,22 @@ >> # @show-menubar: Display the main window menubar. Defaults to "on". >> # (Since 8.0) >> # >> +# @connectors: List of physical monitor/connector names where the GTK >> +# windows containing the respective graphics virtual consoles >> +# (VCs) are to be placed. If a mapping exists for a VC, it >> +# will be moved to that specific monitor or else it would >> +# not be displayed anywhere and would appear disconnected >> +# to the guest. >> +# (Since 8.1) > Please format like > > # @connectors: List of physical monitor/connector names where the GTK > # windows containing the respective graphics virtual consoles > # (VCs) are to be placed. If a mapping exists for a VC, it will > # be moved to that specific monitor or else it would not be > # displayed anywhere and would appear disconnected to the guest. > # (Since 8.1) > > to blend in with recent commit a937b6aa739 (qapi: Reformat doc comments > to conform to current conventions). > > The meaning of @connectors is less than clear. The phrase "If a mapping > exists for a VC" suggests it is a mapping of sorts. "List of physical > monitor/connector names" indicates it maps to physical monitor / > connector name. What does it map from? VC number? How are VCs > numbered? Is it the same number we use in QOM /backend/console[NUM]? > > Using a list for the mapping means the mapping must be dense, e.g. I > can't map #0 and #2 but not #1. Is this what we want? > > The sentence "If a mapping exists" confusing has a dangling else > ambiguity of sorts. I can interpret it as > > If a mapping exists for a VC: > the window will be moved to that specific monitor > or else it would not be displayed anywhere and would appear ... > > or as > > If a mapping exists for a VC: > the window will be moved to that specific monitor > or else it would not be displayed anywhere and would appear ... > > I think we have three cases: > > 0. No mapping provided > > 1. Mapping provided, and the named monitor / connector exists > > 2. Mapping provided, and the named monitor / connector does not exist > > We can go from case 1 to 2 (disconnect) and vice versa (connect) at any > time. > > Please spell out behavior for each case, and for the transitions between > case 1 and 2. > >> +# >> # Since: 2.12 >> ## >> { 'struct' : 'DisplayGTK', >> 'data' : { '*grab-on-hover' : 'bool', >> '*zoom-to-fit' : 'bool', >> '*show-tabs' : 'bool', >> - '*show-menubar' : 'bool' } } >> + '*show-menubar' : 'bool', >> + '*connectors' : ['str'] } } >> >> ## >> # @DisplayEGLHeadless: > [...] >
"Kim, Dongwon" <dongwon.kim@intel.com> writes: > Hi Markus, > > So I've worked on the description of this param. Can you check if this new version looks ok? > > # @connectors: List of physical monitor/connector names where the GTK > # windows containing the respective graphics virtual consoles (VCs) > # are to be placed. Index of the connector name in the array directly > # indicates the id of the VC. > # For example, with "-device gtk,connectors.0=DP-1, connectors.1=DP-2", > # a physical display connected to DP-1 port will be the target monitor > # for VC0 and the one on DP-2 will be the target for VC1. If there is > # no connector associated with a VC, then that VC won't be placed anywhere > # before the QEMU is relaunched with a proper connector name set for it. > # If a connector name exists for a VC but the display cable is not plugged > # in when guest is launched, the VC will be just hidden but will show up > # as soon as the cable is plugged in. If a display is connected in the beginning > # but later disconnected, VC will immediately be hidden and guest will detect > # it as a disconnected display. This option does not force 1 to 1 mapping > # between the connector and the VC, which means multiple VCs can be placed > # on the same display but vice versa is not possible (a single VC duplicated > # on a multiple displays) > # (Since 8.1) Better! Suggest to replace "that VC won't be placed anywhere" by "that VC won't be displayed". Ignorant questions: 1. How would I plug / unplug display cables? 2. If I connect multiple VCs to the same display, what will I see? Are they multiplexed somehow? Old question not yet answered: Using a list for the mapping means the mapping must be dense, e.g. I can't map #0 and #2 but not #1. Is this what we want? [...]
On 7/7/2023 7:07 AM, Markus Armbruster wrote: > "Kim, Dongwon" <dongwon.kim@intel.com> writes: > >> Hi Markus, >> >> So I've worked on the description of this param. Can you check if this new version looks ok? >> >> # @connectors: List of physical monitor/connector names where the GTK >> # windows containing the respective graphics virtual consoles (VCs) >> # are to be placed. Index of the connector name in the array directly >> # indicates the id of the VC. >> # For example, with "-device gtk,connectors.0=DP-1, connectors.1=DP-2", >> # a physical display connected to DP-1 port will be the target monitor >> # for VC0 and the one on DP-2 will be the target for VC1. If there is >> # no connector associated with a VC, then that VC won't be placed anywhere >> # before the QEMU is relaunched with a proper connector name set for it. >> # If a connector name exists for a VC but the display cable is not plugged >> # in when guest is launched, the VC will be just hidden but will show up >> # as soon as the cable is plugged in. If a display is connected in the beginning >> # but later disconnected, VC will immediately be hidden and guest will detect >> # it as a disconnected display. This option does not force 1 to 1 mapping >> # between the connector and the VC, which means multiple VCs can be placed >> # on the same display but vice versa is not possible (a single VC duplicated >> # on a multiple displays) >> # (Since 8.1) > Better! > > Suggest to replace "that VC won't be placed anywhere" by "that VC won't > be displayed". yeah, I will update it in v2 and send the new patch shortly. > > Ignorant questions: > > 1. How would I plug / unplug display cables? I am not sure if I understood your question correctly but 1 or more guest displays (GTK windows) are bound to a certain physical displays like HDMI or DP monitors. So plug/unplug means we disconnect those physical HDMI or DP cables manually. Or this manual hot plug in can be emulated by you write something to sysfs depending on what display driver you use. > > 2. If I connect multiple VCs to the same display, what will I see? Are > they multiplexed somehow? Yeah multiple VCs will be shown on that display. But those could be overlapped since those are all placed at (0, 0) of display in many cases.. but this all depends on how the windows manager determines the starting locations. > > Old question not yet answered: Using a list for the mapping means the > mapping must be dense, e.g. I can't map #0 and #2 but not #1. Is this > what we want? No, it doesn't have to be dense. In your example, you can just leave the place for VC1 blank. For example, you could do connectors.0=DP-1,connectors.2=HDMI-1. But in this case, VC1 won't be activated and stay as disconnected from guest's perspective. I think this info is also needed in v2. > > [...] >
"Kim, Dongwon" <dongwon.kim@intel.com> writes: > On 7/7/2023 7:07 AM, Markus Armbruster wrote: >> "Kim, Dongwon" <dongwon.kim@intel.com> writes: >> >>> Hi Markus, >>> >>> So I've worked on the description of this param. Can you check if this new version looks ok? >>> >>> # @connectors: List of physical monitor/connector names where the GTK >>> # windows containing the respective graphics virtual consoles (VCs) >>> # are to be placed. Index of the connector name in the array directly >>> # indicates the id of the VC. >>> # For example, with "-device gtk,connectors.0=DP-1, connectors.1=DP-2", >>> # a physical display connected to DP-1 port will be the target monitor >>> # for VC0 and the one on DP-2 will be the target for VC1. If there is >>> # no connector associated with a VC, then that VC won't be placed anywhere >>> # before the QEMU is relaunched with a proper connector name set for it. >>> # If a connector name exists for a VC but the display cable is not plugged >>> # in when guest is launched, the VC will be just hidden but will show up >>> # as soon as the cable is plugged in. If a display is connected in the beginning >>> # but later disconnected, VC will immediately be hidden and guest will detect >>> # it as a disconnected display. This option does not force 1 to 1 mapping >>> # between the connector and the VC, which means multiple VCs can be placed >>> # on the same display but vice versa is not possible (a single VC duplicated >>> # on a multiple displays) >>> # (Since 8.1) >> Better! >> >> Suggest to replace "that VC won't be placed anywhere" by "that VC won't >> be displayed". > > yeah, I will update it in v2 and send the new patch shortly. > >> >> Ignorant questions: >> >> 1. How would I plug / unplug display cables? > > I am not sure if I understood your question correctly but 1 or more guest displays (GTK windows) are bound to a certain physical displays like HDMI or DP monitors. So plug/unplug means we disconnect those physical HDMI or DP cables manually. Or this manual hot plug in can be emulated by you write something to sysfs depending on what display driver you use. Let's see whether I understand. A VC is placed on a *physical* monitor, i.e. a window appears on that monitor. That monitor's plug / unplug state is passed through to the guest, i.e. if I physically unplug / plug the monitor, the guest sees an unplug / plug of its virtual monitor. Correct? Permit me another ignorant question... Say I have a single monitor. I configured my X windows manager to show four virtual desktops. Can I use your feature to direct on which virtual desktop each VC is placed? >> 2. If I connect multiple VCs to the same display, what will I see? Are >> they multiplexed somehow? > > Yeah multiple VCs will be shown on that display. But those could be overlapped since those are all placed at (0, 0) of display in many cases.. but this all depends on how the windows manager determines the starting locations. Got it, thanks! >> Old question not yet answered: Using a list for the mapping means the >> mapping must be dense, e.g. I can't map #0 and #2 but not #1. Is this >> what we want? > > No, it doesn't have to be dense. In your example, you can just leave the place for VC1 blank. For example, you could do connectors.0=DP-1,connectors.2=HDMI-1. But in this case, VC1 won't be activated and stay as disconnected from guest's perspective. I think this info is also needed in v2. Have you tried this? I believe it'll fail with something like "Parameter 'connectors.1' missing". >> [...]
On 7/9/2023 11:05 PM, Markus Armbruster wrote: > "Kim, Dongwon" <dongwon.kim@intel.com> writes: > >> On 7/7/2023 7:07 AM, Markus Armbruster wrote: >>> "Kim, Dongwon" <dongwon.kim@intel.com> writes: >>> >>>> Hi Markus, >>>> >>>> So I've worked on the description of this param. Can you check if this new version looks ok? >>>> >>>> # @connectors: List of physical monitor/connector names where the GTK >>>> # windows containing the respective graphics virtual consoles (VCs) >>>> # are to be placed. Index of the connector name in the array directly >>>> # indicates the id of the VC. >>>> # For example, with "-device gtk,connectors.0=DP-1, connectors.1=DP-2", >>>> # a physical display connected to DP-1 port will be the target monitor >>>> # for VC0 and the one on DP-2 will be the target for VC1. If there is >>>> # no connector associated with a VC, then that VC won't be placed anywhere >>>> # before the QEMU is relaunched with a proper connector name set for it. >>>> # If a connector name exists for a VC but the display cable is not plugged >>>> # in when guest is launched, the VC will be just hidden but will show up >>>> # as soon as the cable is plugged in. If a display is connected in the beginning >>>> # but later disconnected, VC will immediately be hidden and guest will detect >>>> # it as a disconnected display. This option does not force 1 to 1 mapping >>>> # between the connector and the VC, which means multiple VCs can be placed >>>> # on the same display but vice versa is not possible (a single VC duplicated >>>> # on a multiple displays) >>>> # (Since 8.1) >>> Better! >>> >>> Suggest to replace "that VC won't be placed anywhere" by "that VC won't >>> be displayed". >> yeah, I will update it in v2 and send the new patch shortly. >> >>> Ignorant questions: >>> >>> 1. How would I plug / unplug display cables? >> I am not sure if I understood your question correctly but 1 or more guest displays (GTK windows) are bound to a certain physical displays like HDMI or DP monitors. So plug/unplug means we disconnect those physical HDMI or DP cables manually. Or this manual hot plug in can be emulated by you write something to sysfs depending on what display driver you use. > Let's see whether I understand. > > A VC is placed on a *physical* monitor, i.e. a window appears on that > monitor. That monitor's plug / unplug state is passed through to the > guest, i.e. if I physically unplug / plug the monitor, the guest sees an > unplug / plug of its virtual monitor. Correct? This is correct. When a display is disconnected, "monitor-changed" GTK event will be triggered then it will call gd_ui_size(0,0) which makes the guest display connection status to "disconnected". > > Permit me another ignorant question... Say I have a single monitor. I > configured my X windows manager to show four virtual desktops. Can I > use your feature to direct on which virtual desktop each VC is placed? Would those virtual desktops will be their own connector names like HDMI or DP? We use the connector name for the actual physical display you see when running xrandr. I don't know how virtual desktops are created and managed but if they don't have their own connector names that GTK API can read, it won't work within our current implementation. > >>> 2. If I connect multiple VCs to the same display, what will I see? Are >>> they multiplexed somehow? >> Yeah multiple VCs will be shown on that display. But those could be overlapped since those are all placed at (0, 0) of display in many cases.. but this all depends on how the windows manager determines the starting locations. > Got it, thanks! > >>> Old question not yet answered: Using a list for the mapping means the >>> mapping must be dense, e.g. I can't map #0 and #2 but not #1. Is this >>> what we want? >> No, it doesn't have to be dense. In your example, you can just leave the place for VC1 blank. For example, you could do connectors.0=DP-1,connectors.2=HDMI-1. But in this case, VC1 won't be activated and stay as disconnected from guest's perspective. I think this info is also needed in v2. > Have you tried this? I believe it'll fail with something like > "Parameter 'connectors.1' missing". Just tested it. Yeah you are correct. I think I had a bad assumption. Let me take a look to see if I can make it work as I assumed. > >>> [...]
"Kim, Dongwon" <dongwon.kim@intel.com> writes: > On 7/9/2023 11:05 PM, Markus Armbruster wrote: >> "Kim, Dongwon" <dongwon.kim@intel.com> writes: >> >>> On 7/7/2023 7:07 AM, Markus Armbruster wrote: [...] >>>> Ignorant questions: >>>> >>>> 1. How would I plug / unplug display cables? >>> >>> I am not sure if I understood your question correctly but 1 or more guest displays (GTK windows) are bound to a certain physical displays like HDMI or DP monitors. So plug/unplug means we disconnect those physical HDMI or DP cables manually. Or this manual hot plug in can be emulated by you write something to sysfs depending on what display driver you use. >> >> Let's see whether I understand. >> >> A VC is placed on a *physical* monitor, i.e. a window appears on that >> monitor. That monitor's plug / unplug state is passed through to the >> guest, i.e. if I physically unplug / plug the monitor, the guest sees an >> unplug / plug of its virtual monitor. Correct? > > This is correct. When a display is disconnected, "monitor-changed" GTK event will be triggered then it will call gd_ui_size(0,0) which makes the guest display connection status to "disconnected". Thanks! >> Permit me another ignorant question... Say I have a single monitor. I >> configured my X windows manager to show four virtual desktops. Can I >> use your feature to direct on which virtual desktop each VC is placed? > > Would those virtual desktops will be their own connector names like HDMI or DP? We use the connector name for the actual physical display you see when running xrandr. Output of xrandr is identical on different virtual desktops for me. > I don't know how virtual desktops are created and managed but if they don't have their own connector names that GTK API can read, it won't work within our current implementation. After searching around a bit... Virtual desktops, a.k.a. workspaces, are a window manager thing. Completely different from X displays and monitors. Programs can mess with placement (wmctrl does). No idea whether GDK provides an interface for it. No need to discuss this further at this time. [...] >>>> Old question not yet answered: Using a list for the mapping means the >>>> mapping must be dense, e.g. I can't map #0 and #2 but not #1. Is this >>>> what we want? >>> >>> No, it doesn't have to be dense. In your example, you can just leave the place for VC1 blank. For example, you could do connectors.0=DP-1,connectors.2=HDMI-1. But in this case, VC1 won't be activated and stay as disconnected from guest's perspective. I think this info is also needed in v2. >> >> Have you tried this? I believe it'll fail with something like >> "Parameter 'connectors.1' missing". > > Just tested it. Yeah you are correct. I think I had a bad assumption. Let me take a look to see if I can make it work as I assumed. If sparse mappings make sense, we should provide for them, I think. An array like '*connectors': ['str'] maps from integers 0, 1, ... It can't do sparse (you can't omit integers in the middle). Instead of omitting them, we could map them to null: '*connectors': ['StrOrNull']. JSON input looks like [null, "HDMI-A-0"]. Since dotted key syntax does not support null at this time, you'd have to use JSON. Only an object can do sparse. However, the QAPI schema language can't express "object where the keys are integers and the values are strings". We'd have to use 'any', and check everything manually. Hmm. Thoughts? >>>> [...]
On 7/10/2023 11:36 PM, Markus Armbruster wrote: > "Kim, Dongwon" <dongwon.kim@intel.com> writes: > >> On 7/9/2023 11:05 PM, Markus Armbruster wrote: >>> "Kim, Dongwon" <dongwon.kim@intel.com> writes: >>> >>>> On 7/7/2023 7:07 AM, Markus Armbruster wrote: > [...] > >>>>> Ignorant questions: >>>>> >>>>> 1. How would I plug / unplug display cables? >>>> I am not sure if I understood your question correctly but 1 or more guest displays (GTK windows) are bound to a certain physical displays like HDMI or DP monitors. So plug/unplug means we disconnect those physical HDMI or DP cables manually. Or this manual hot plug in can be emulated by you write something to sysfs depending on what display driver you use. >>> Let's see whether I understand. >>> >>> A VC is placed on a *physical* monitor, i.e. a window appears on that >>> monitor. That monitor's plug / unplug state is passed through to the >>> guest, i.e. if I physically unplug / plug the monitor, the guest sees an >>> unplug / plug of its virtual monitor. Correct? >> This is correct. When a display is disconnected, "monitor-changed" GTK event will be triggered then it will call gd_ui_size(0,0) which makes the guest display connection status to "disconnected". > Thanks! > >>> Permit me another ignorant question... Say I have a single monitor. I >>> configured my X windows manager to show four virtual desktops. Can I >>> use your feature to direct on which virtual desktop each VC is placed? >> Would those virtual desktops will be their own connector names like HDMI or DP? We use the connector name for the actual physical display you see when running xrandr. > Output of xrandr is identical on different virtual desktops for me. > >> I don't know how virtual desktops are created and managed but if they don't have their own connector names that GTK API can read, it won't work within our current implementation. > After searching around a bit... Virtual desktops, a.k.a. workspaces, > are a window manager thing. Completely different from X displays and > monitors. Programs can mess with placement (wmctrl does). No idea > whether GDK provides an interface for it. No need to discuss this > further at this time. > > [...] > >>>>> Old question not yet answered: Using a list for the mapping means the >>>>> mapping must be dense, e.g. I can't map #0 and #2 but not #1. Is this >>>>> what we want? >>>> No, it doesn't have to be dense. In your example, you can just leave the place for VC1 blank. For example, you could do connectors.0=DP-1,connectors.2=HDMI-1. But in this case, VC1 won't be activated and stay as disconnected from guest's perspective. I think this info is also needed in v2. >>> Have you tried this? I believe it'll fail with something like >>> "Parameter 'connectors.1' missing". >> Just tested it. Yeah you are correct. I think I had a bad assumption. Let me take a look to see if I can make it work as I assumed. > If sparse mappings make sense, we should provide for them, I think. > > An array like '*connectors': ['str'] maps from integers 0, 1, ... It > can't do sparse (you can't omit integers in the middle). Yeah, I understand this now. Despite of my initial intention was different, I am wondering if we don't allow the sparse mapping in this implementation. Any thought on that? The V2 patch is with that change in the description. Another thing I think is to change QAPI design little bit to make it fill the element with null (0) if it's not given. Would this be a feasible option? > > Instead of omitting them, we could map them to null: '*connectors': > ['StrOrNull']. JSON input looks like [null, "HDMI-A-0"]. Since dotted > key syntax does not support null at this time, you'd have to use JSON. > > Only an object can do sparse. However, the QAPI schema language can't > express "object where the keys are integers and the values are strings". > We'd have to use 'any', and check everything manually. > > Hmm. Thoughts? > >>>>> [...]
"Kim, Dongwon" <dongwon.kim@intel.com> writes: > On 7/10/2023 11:36 PM, Markus Armbruster wrote: >> "Kim, Dongwon" <dongwon.kim@intel.com> writes: >> >>> On 7/9/2023 11:05 PM, Markus Armbruster wrote: >>>> "Kim, Dongwon" <dongwon.kim@intel.com> writes: >>>> >>>>> On 7/7/2023 7:07 AM, Markus Armbruster wrote: [...] >>>>>> Old question not yet answered: Using a list for the mapping means the >>>>>> mapping must be dense, e.g. I can't map #0 and #2 but not #1. Is this >>>>>> what we want? >>>>> >>>>> No, it doesn't have to be dense. In your example, you can just leave the place for VC1 blank. For example, you could do connectors.0=DP-1,connectors.2=HDMI-1. But in this case, VC1 won't be activated and stay as disconnected from guest's perspective. I think this info is also needed in v2. >>>> >>>> Have you tried this? I believe it'll fail with something like >>>> "Parameter 'connectors.1' missing". >>> >>> Just tested it. Yeah you are correct. I think I had a bad assumption. Let me take a look to see if I can make it work as I assumed. >> >> If sparse mappings make sense, we should provide for them, I think. >> >> An array like '*connectors': ['str'] maps from integers 0, 1, ... It >> can't do sparse (you can't omit integers in the middle). > > Yeah, I understand this now. Despite of my initial intention was different, I am wondering if we don't allow the sparse mapping in this implementation. Any thought on that? Repeating myself: if sparse mappings make sense, we should provide for them, I think. So, do they make sense? Or asked differently, could a user conceivably want to *not* place a VC? > The V2 patch is with that change in the description. Another thing I think is to change QAPI design little bit to make it fill the element with null (0) if it's not given. Would this be a feasible option? A 'str' cannot be NULL. In fact, no QAPI type can be null, except for 'null' (which is always NULL), alternates with a 'null' branch, and pointer-valued optionals (which are null when absent). >> Instead of omitting them, we could map them to null: '*connectors': >> ['StrOrNull']. JSON input looks like [null, "HDMI-A-0"]. Since dotted >> key syntax does not support null at this time, you'd have to use JSON. >> >> Only an object can do sparse. However, the QAPI schema language can't >> express "object where the keys are integers and the values are strings". >> We'd have to use 'any', and check everything manually. >> >> Hmm. Thoughts? >> >>>>>> [...]
On 7/11/2023 10:52 PM, Markus Armbruster wrote: > "Kim, Dongwon" <dongwon.kim@intel.com> writes: > >> On 7/10/2023 11:36 PM, Markus Armbruster wrote: >>> "Kim, Dongwon" <dongwon.kim@intel.com> writes: >>> >>>> On 7/9/2023 11:05 PM, Markus Armbruster wrote: >>>>> "Kim, Dongwon" <dongwon.kim@intel.com> writes: >>>>> >>>>>> On 7/7/2023 7:07 AM, Markus Armbruster wrote: > [...] > >>>>>>> Old question not yet answered: Using a list for the mapping means the >>>>>>> mapping must be dense, e.g. I can't map #0 and #2 but not #1. Is this >>>>>>> what we want? >>>>>> No, it doesn't have to be dense. In your example, you can just leave the place for VC1 blank. For example, you could do connectors.0=DP-1,connectors.2=HDMI-1. But in this case, VC1 won't be activated and stay as disconnected from guest's perspective. I think this info is also needed in v2. >>>>> Have you tried this? I believe it'll fail with something like >>>>> "Parameter 'connectors.1' missing". >>>> Just tested it. Yeah you are correct. I think I had a bad assumption. Let me take a look to see if I can make it work as I assumed. >>> If sparse mappings make sense, we should provide for them, I think. >>> >>> An array like '*connectors': ['str'] maps from integers 0, 1, ... It >>> can't do sparse (you can't omit integers in the middle). >> Yeah, I understand this now. Despite of my initial intention was different, I am wondering if we don't allow the sparse mapping in this implementation. Any thought on that? > Repeating myself: if sparse mappings make sense, we should provide for > them, I think. > So, do they make sense? Or asked differently, could a user conceivably > want to *not* place a VC? It should be very rare. I can't think of any valid use case other than test cases for validating this feature. If VC is not mapped to anything from the beginning, there is no way to get it displayed. So there is no value to do so. So I assume provisioning a full list as a requirement would make sense here. >> The V2 patch is with that change in the description. Another thing I think is to change QAPI design little bit to make it fill the element with null (0) if it's not given. Would this be a feasible option? > A 'str' cannot be NULL. In fact, no QAPI type can be null, except for > 'null' (which is always NULL), alternates with a 'null' branch, and > pointer-valued optionals (which are null when absent). > >>> Instead of omitting them, we could map them to null: '*connectors': >>> ['StrOrNull']. JSON input looks like [null, "HDMI-A-0"]. Since dotted >>> key syntax does not support null at this time, you'd have to use JSON. >>> >>> Only an object can do sparse. However, the QAPI schema language can't >>> express "object where the keys are integers and the values are strings". >>> We'd have to use 'any', and check everything manually. >>> >>> Hmm. Thoughts? >>> >>>>>>> [...]
diff --git a/include/ui/gtk.h b/include/ui/gtk.h index e7c4726aad..189817ab88 100644 --- a/include/ui/gtk.h +++ b/include/ui/gtk.h @@ -84,6 +84,7 @@ typedef struct VirtualConsole { GtkWidget *menu_item; GtkWidget *tab_item; GtkWidget *focus; + GdkMonitor *monitor; VirtualConsoleType type; union { VirtualGfxConsole gfx; diff --git a/qapi/ui.json b/qapi/ui.json index 2755395483..0f5ab35bae 100644 --- a/qapi/ui.json +++ b/qapi/ui.json @@ -1315,13 +1315,22 @@ # @show-menubar: Display the main window menubar. Defaults to "on". # (Since 8.0) # +# @connectors: List of physical monitor/connector names where the GTK +# windows containing the respective graphics virtual consoles +# (VCs) are to be placed. If a mapping exists for a VC, it +# will be moved to that specific monitor or else it would +# not be displayed anywhere and would appear disconnected +# to the guest. +# (Since 8.1) +# # Since: 2.12 ## { 'struct' : 'DisplayGTK', 'data' : { '*grab-on-hover' : 'bool', '*zoom-to-fit' : 'bool', '*show-tabs' : 'bool', - '*show-menubar' : 'bool' } } + '*show-menubar' : 'bool', + '*connectors' : ['str'] } } ## # @DisplayEGLHeadless: diff --git a/qemu-options.hx b/qemu-options.hx index b57489d7ca..2eb0d6a129 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -2044,7 +2044,7 @@ DEF("display", HAS_ARG, QEMU_OPTION_display, #if defined(CONFIG_GTK) "-display gtk[,full-screen=on|off][,gl=on|off][,grab-on-hover=on|off]\n" " [,show-tabs=on|off][,show-cursor=on|off][,window-close=on|off]\n" - " [,show-menubar=on|off]\n" + " [,show-menubar=on|off][,connectors.<index>=<connector name>]\n" #endif #if defined(CONFIG_VNC) "-display vnc=<display>[,<optargs>]\n" @@ -2139,6 +2139,9 @@ SRST ``show-menubar=on|off`` : Display the main window menubar, defaults to "on" + ``connectors=<conn name>`` : VC to connector mappings to display the VC + window on a specific monitor + ``curses[,charset=<encoding>]`` Display video output via curses. For graphics device models which support a text mode, QEMU can display this output using a diff --git a/ui/gtk.c b/ui/gtk.c index d8323a3a9d..f4c71454a3 100644 --- a/ui/gtk.c +++ b/ui/gtk.c @@ -38,6 +38,7 @@ #include "qemu/cutils.h" #include "qemu/error-report.h" #include "qemu/main-loop.h" +#include "qemu/option.h" #include "ui/console.h" #include "ui/gtk.h" @@ -741,6 +742,39 @@ static void gd_set_ui_size(VirtualConsole *vc, gint width, gint height) dpy_set_ui_info(vc->gfx.dcl.con, &info, true); } +static void gd_ui_hide(VirtualConsole *vc) +{ + QemuUIInfo info; + + vc->gfx.visible = false; + info = *dpy_get_ui_info(vc->gfx.dcl.con); + info.width = 0; + info.height = 0; + dpy_set_ui_info(vc->gfx.dcl.con, &info, false); +} + +static void gd_ui_show(VirtualConsole *vc) +{ + QemuUIInfo info; + GtkDisplayState *s = vc->s; + GdkWindow *window = gtk_widget_get_window(vc->gfx.drawing_area); + + info = *dpy_get_ui_info(vc->gfx.dcl.con); + info.width = gdk_window_get_width(window); + info.height = gdk_window_get_height(window); + dpy_set_ui_info(vc->gfx.dcl.con, &info, false); + + if (gd_is_grab_active(s)) { + gd_grab_keyboard(vc, "user-request-main-window"); + gd_grab_pointer(vc, "user-request-main-window"); + } else { + gd_ungrab_keyboard(s); + gd_ungrab_pointer(s); + } + + vc->gfx.visible = true; +} + #if defined(CONFIG_OPENGL) static gboolean gd_render_event(GtkGLArea *area, GdkGLContext *context, @@ -1352,12 +1386,10 @@ static void gd_menu_switch_vc(GtkMenuItem *item, void *opaque) GtkDisplayState *s = opaque; VirtualConsole *vc; GtkNotebook *nb = GTK_NOTEBOOK(s->notebook); - GdkWindow *window; gint page; vc = gd_vc_find_current(s); - vc->gfx.visible = false; - gd_set_ui_size(vc, 0, 0); + gd_ui_hide(vc); vc = gd_vc_find_by_menu(s); gtk_release_modifiers(s); @@ -1365,10 +1397,7 @@ static void gd_menu_switch_vc(GtkMenuItem *item, void *opaque) page = gtk_notebook_page_num(nb, vc->tab_item); gtk_notebook_set_current_page(nb, page); gtk_widget_grab_focus(vc->focus); - window = gtk_widget_get_window(vc->gfx.drawing_area); - gd_set_ui_size(vc, gdk_window_get_width(window), - gdk_window_get_height(window)); - vc->gfx.visible = true; + gd_ui_show(vc); } } @@ -1398,8 +1427,8 @@ static gboolean gd_tab_window_close(GtkWidget *widget, GdkEvent *event, VirtualConsole *vc = opaque; GtkDisplayState *s = vc->s; - vc->gfx.visible = false; - gd_set_ui_size(vc, 0, 0); + gd_ui_hide(vc); + dpy_gl_scanout_disable(vc->gfx.dcl.con); gtk_widget_set_sensitive(vc->menu_item, true); gd_widget_reparent(vc->window, s->notebook, vc->tab_item); @@ -1430,20 +1459,14 @@ static gboolean gd_window_state_event(GtkWidget *widget, GdkEvent *event, } if (event->window_state.new_window_state & GDK_WINDOW_STATE_ICONIFIED) { - vc->gfx.visible = false; - gd_set_ui_size(vc, 0, 0); + gd_ui_hide(vc); if (vc->gfx.guest_fb.dmabuf && vc->gfx.guest_fb.dmabuf->draw_submitted) { vc->gfx.guest_fb.dmabuf->draw_submitted = false; graphic_hw_gl_block(vc->gfx.dcl.con, false); } } else { - GdkWindow *window; - window = gtk_widget_get_window(vc->gfx.drawing_area); - gd_set_ui_size(vc, gdk_window_get_width(window), - gdk_window_get_height(window)); - - vc->gfx.visible = true; + gd_ui_show(vc); } return TRUE; @@ -1503,7 +1526,6 @@ static void gd_tab_window_create(VirtualConsole *vc) static void gd_menu_untabify(GtkMenuItem *item, void *opaque) { GtkDisplayState *s = opaque; - GdkWindow *window; VirtualConsole *vc = gd_vc_find_current(s); if (vc->type == GD_VC_GFX && @@ -1515,10 +1537,205 @@ static void gd_menu_untabify(GtkMenuItem *item, void *opaque) gd_tab_window_create(vc); } - window = gtk_widget_get_window(vc->gfx.drawing_area); - gd_set_ui_size(vc, gdk_window_get_width(window), - gdk_window_get_height(window)); - vc->gfx.visible = true; + gd_ui_show(vc); +} + +static void gd_window_show_on_monitor(GdkDisplay *dpy, VirtualConsole *vc, + gint monitor_num) +{ + GtkDisplayState *s = vc->s; + GdkMonitor *monitor = gdk_display_get_monitor(dpy, monitor_num); + GdkRectangle geometry; + + if (!vc->window) { + gd_tab_window_create(vc); + } + + gdk_window_show(gtk_widget_get_window(vc->window)); + gd_update_windowsize(vc); + gdk_monitor_get_geometry(monitor, &geometry); + /* + * Note: some compositors (mainly Wayland ones) may not honor a + * request to move to a particular location. The user is expected + * to drag the window to the preferred location in this case. + */ + gtk_window_move(GTK_WINDOW(vc->window), geometry.x, geometry.y); + + if (s->opts->has_full_screen && s->opts->full_screen) { + gtk_widget_set_size_request(vc->gfx.drawing_area, -1, -1); + gtk_window_fullscreen(GTK_WINDOW(vc->window)); + } + + vc->monitor = monitor; + gd_ui_show(vc); + + if (vc->window) { + g_signal_connect(vc->window, "window-state-event", + G_CALLBACK(gd_window_state_event), vc); + } + + gd_update_cursor(vc); +} + +static int gd_monitor_lookup(GdkDisplay *dpy, char *label) +{ + GdkMonitor *monitor; + int total_monitors = gdk_display_get_n_monitors(dpy); + int i; + + for (i = 0; i < total_monitors; i++) { + monitor = gdk_display_get_monitor(dpy, i); + if (monitor && !g_strcmp0(gdk_monitor_get_model(monitor), label)) { + return i; + } + } + return -1; +} + +static gboolean gd_vc_is_misplaced(GdkDisplay *dpy, GdkMonitor *monitor, + VirtualConsole *vc) +{ + GdkWindow *window = gtk_widget_get_window(vc->gfx.drawing_area); + GdkMonitor *mon = gdk_display_get_monitor_at_window(dpy, window); + const char *monitor_name = gdk_monitor_get_model(monitor); + + if (!vc->monitor) { + if (!g_strcmp0(monitor_name, vc->label)) { + return TRUE; + } + } else { + if (mon && mon != vc->monitor) { + return TRUE; + } + } + return FALSE; +} + +static void gd_vc_add_remove_monitor(GdkDisplay *dpy, GtkDisplayState *s) +{ + VirtualConsole *vc; + GdkMonitor *monitor; + gint monitor_num; + int i; + + /* + * We need to call gd_vc_is_misplaced() after a monitor is added to + * ensure that the Host compositor has not moved our windows around. + */ + for (i = 0; i < s->nb_vcs; i++) { + vc = &s->vc[i]; + if (vc->label) { + monitor_num = gd_monitor_lookup(dpy, vc->label); + if (monitor_num >= 0) { + monitor = gdk_display_get_monitor(dpy, monitor_num); + if (gd_vc_is_misplaced(dpy, monitor, vc)) { + gd_window_show_on_monitor(dpy, vc, monitor_num); + } + } else if (vc->monitor) { + vc->monitor = NULL; + if (vc->window) { + g_signal_handlers_disconnect_by_func(vc->window, + G_CALLBACK(gd_window_state_event), + vc); + } + + gd_ui_hide(vc); + /* if window exist, hide it */ + if (vc->window) { + gdk_window_hide(gtk_widget_get_window(vc->window)); + } + } + } + } +} + +static void gd_monitors_reset_timer(void *opaque) +{ + GtkDisplayState *s = opaque; + GdkDisplay *dpy = gdk_display_get_default(); + + gd_vc_add_remove_monitor(dpy, s); +} + +static void gd_monitors_changed(GdkScreen *scr, void *opaque) +{ + GtkDisplayState *s = opaque; + QEMUTimer *mon_reset_timer; + + mon_reset_timer = timer_new_ms(QEMU_CLOCK_REALTIME, + gd_monitors_reset_timer, s); + timer_mod(mon_reset_timer, + qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 2000); +} + +static VirtualConsole *gd_next_gfx_vc(GtkDisplayState *s) +{ + VirtualConsole *vc; + int i; + + for (i = 0; i < s->nb_vcs; i++) { + vc = &s->vc[i]; + if (vc->type == GD_VC_GFX && + qemu_console_is_graphic(vc->gfx.dcl.con) && + !vc->label) { + return vc; + } + } + return NULL; +} + +static void gd_vc_free_labels(GtkDisplayState *s) +{ + VirtualConsole *vc; + int i; + + for (i = 0; i < s->nb_vcs; i++) { + vc = &s->vc[i]; + if (vc->type == GD_VC_GFX && + qemu_console_is_graphic(vc->gfx.dcl.con)) { + g_free(vc->label); + vc->label = NULL; + } + } +} + +static void gd_connectors_init(GdkDisplay *dpy, GtkDisplayState *s) +{ + VirtualConsole *vc; + strList *conn; + gint monitor_num; + gboolean first_vc = TRUE; + + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), + FALSE); + gd_vc_free_labels(s); + for (conn = s->opts->u.gtk.connectors; conn; conn = conn->next) { + vc = gd_next_gfx_vc(s); + if (!vc) { + break; + } + if (first_vc) { + vc->window = s->window; + first_vc = FALSE; + } + + vc->label = g_strdup(conn->value); + monitor_num = gd_monitor_lookup(dpy, vc->label); + if (monitor_num >= 0) { + gd_window_show_on_monitor(dpy, vc, monitor_num); + } else { + if (vc->window) { + g_signal_handlers_disconnect_by_func(vc->window, + G_CALLBACK(gd_window_state_event), + vc); + } + gd_ui_hide(vc); + if (vc->window) { + gdk_window_hide(gtk_widget_get_window(vc->window)); + } + } + } } static void gd_menu_show_menubar(GtkMenuItem *item, void *opaque) @@ -1843,7 +2060,7 @@ static gboolean gd_configure(GtkWidget *widget, VirtualConsole *vc = opaque; if (vc->gfx.visible) { - gd_set_ui_size(vc, cfg->width, cfg->height); + gd_ui_show(vc); } return FALSE; } @@ -2180,6 +2397,10 @@ static void gd_connect_signals(GtkDisplayState *s) G_CALLBACK(gd_menu_grab_input), s); g_signal_connect(s->notebook, "switch-page", G_CALLBACK(gd_change_page), s); + if (s->opts->u.gtk.has_connectors) { + g_signal_connect(gdk_screen_get_default(), "monitors-changed", + G_CALLBACK(gd_monitors_changed), s); + } } static GtkWidget *gd_create_menu_machine(GtkDisplayState *s) @@ -2561,6 +2782,10 @@ static void gtk_display_init(DisplayState *ds, DisplayOptions *opts) opts->u.gtk.show_tabs) { gtk_menu_item_activate(GTK_MENU_ITEM(s->show_tabs_item)); } + + if (s->opts->u.gtk.has_connectors) { + gd_connectors_init(window_display, s); + } #ifdef CONFIG_GTK_CLIPBOARD gd_clipboard_init(s); #endif /* CONFIG_GTK_CLIPBOARD */