diff mbox series

[RFC,6/9] ui/gtk: Add a new parameter to assign connectors/monitors to GFX VCs

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

Commit Message

Kim, Dongwon June 21, 2023, 12:43 a.m. UTC
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>
---
 include/ui/gtk.h |   1 +
 qapi/ui.json     |  11 +-
 qemu-options.hx  |   5 +-
 ui/gtk.c         | 271 +++++++++++++++++++++++++++++++++++++++++++----
 4 files changed, 263 insertions(+), 25 deletions(-)

Comments

Markus Armbruster June 21, 2023, 5:51 a.m. UTC | #1
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 June 27, 2023, 6:22 p.m. UTC | #2
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:
> [...]
>
Markus Armbruster July 7, 2023, 2:07 p.m. UTC | #3
"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?

[...]
Kim, Dongwon July 7, 2023, 5:16 p.m. UTC | #4
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.

>
> [...]
>
Markus Armbruster July 10, 2023, 6:05 a.m. UTC | #5
"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".

>> [...]
Kim, Dongwon July 10, 2023, 8:31 p.m. UTC | #6
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.

>
>>> [...]
Markus Armbruster July 11, 2023, 6:36 a.m. UTC | #7
"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?

>>>> [...]
Kim, Dongwon July 11, 2023, 5:19 p.m. UTC | #8
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?
>
>>>>> [...]
Markus Armbruster July 12, 2023, 5:52 a.m. UTC | #9
"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?
>>
>>>>>> [...]
Kim, Dongwon July 13, 2023, 4:53 a.m. UTC | #10
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 mbox series

Patch

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 */