project-repo / cagebreak

Cagebreak: A Wayland Tiling Compositor Inspired by Ratpoison
MIT License
284 stars 19 forks source link

Session error upon turning off an output #38

Closed kinleyd closed 10 months ago

kinleyd commented 1 year ago

Hi guys,

Thanks and congratulations on Version 2.0 which I was able to install without any issues.

The current bug existed earlier (in the dev/debug version that I was using) and is present in the new version:

If I turn an output off, I lose my session - everything is closed and a new session is opened.

project-repo commented 1 year ago

Hi kinleyd

Thanks and congratulations on Version 2.0 which I was able to install without any issues.

Thank you, glad to hear!

If I turn an output off, I lose my session configuration. I'm not sure if it's logging me out, but all my open apps/windows are closed, even on outputs that remain powered on.

Yes, this is something that has bothering me for a while too, but up until now we haven't had the time to look into it. It isn't immediately a bug in the sense that the documentation states that cagebreak exits when there is no enabled output available, however it is obviously somewhat of an annoyance. We're not sure when/how we will be able to fix this, but inputs are very welcome. In any case, thanks for bringing it up, which has moved this to a higher position on the priority list.

Cheers, project-repo

project-repo commented 1 year ago

Hi kinleyd

we have considered the problem and decided to proceed as follows: When the last output is terminated, cagebreak will continue to run in headless mode. Then, once an output is added later, all views will be moved to this output on workspace 1 in full screen mode. This way, the views are preserved even when all outputs are turned off, although the specific workspace/tile layout will be lost.

What do you think?

cheers project-repo

kinleyd commented 1 year ago

Thanks for getting back on this. I think the proposed solution will not be ideal. In my use case I have the habit of switching off a monitor sometimes - for example when I'm watching a movie or other video and don't want the distraction of multiple monitors being on. It would be much preferable to be able to just turn the monitor back on and have all the work spaces, windows and tiles still there.

project-repo commented 1 year ago

Hi kinleyd

we have now partially discussed this problem.

If the last remaining output goes away, it might be possible (we haven't investigated all implementation issues yet) to maintain the workspaces etc. iff the output which is reattached has the exact same resolution. Otherwise everything will be put on workspace 1 as outlined above.

If you just want to randomly turn off/on outputs and maintain everything in the background regardless of circumstance, as it seems from your comment, this unfortunately will not be possible, since all views will immediately be moved to the current output when an output is turned off, as long as another one is available. You might be able to script something together to "save" the screen composition before you remove the output and then reinstate it once the output has been reestablished, though for the time being, we leave this as an exercise to the user.

As always, we are open to any other suggestions.

cheers project-repo

kinleyd commented 1 year ago

Thank you for your response. Yes, it is support for the random switching on and off of monitors that I was looking for. Pity it can't be supported.

Does Xorg handle it by saving screen state?

project-repo commented 1 year ago

Hi kinleyd

I'm actually not sure how Xorg handles this. What happens if you disconnect an output under Xorg?

Just to be sure: the only remaining concern is that when one disconnects a monitor and then reconnects it later, it looses its configuration, right? What exactly would you want to happen in this case?

Sorry for the long response time. This is due to an internal communication problem.

cheers project-repo

kinleyd commented 1 year ago

I'm actually not sure how Xorg handles this. What happens if you disconnect an output under Xorg?

It just resumes wherever you left off. In fact everything is still active (mouse clicks, keypresses, etc) if the disconnected output is selected as the active area.

Just to be sure: the only remaining concern is that when one disconnects a monitor and then reconnects it later, it looses its configuration, right?

Yes.

What exactly would you want to happen in this case?

Like with xorg, just resume with the configuration before the disconnection.

Sorry for the long response time. This is due to an internal communication problem.

No problem at all!

sodface commented 1 year ago

Hello, I maintain the Cagebreak package for Alpine but I've only just recently started to use Cagebreak myself and so far I'm really loving it!

I'm also in the habit of powering off monitors and have noticed that the Cagebreak session ends so I agree it would be nice to have some more graceful behavior when an output is lost.

The previous sesssion IPC socket also is not cleaned up:

srwx------    1 sodface  sodface        0 Apr 11 17:49 cagebreak-ipc.1000.11124.sock
srwx------    1 sodface  sodface        0 Apr  9 15:03 cagebreak-ipc.1000.3137.sock

Off topic, but I wanted to note that Alpine's default nc is provided by busybox which does work with Cagebreak's ipc socket using the local syntax:

echo "dump" | nc local:/tmp/user/1000/cagebreak-ipc.1000.11124.sock
sodface commented 1 year ago

I'm not sure if it's clearly defined in this thread, but it appears, at least in a single monitor setup, that the session doesn't end when the output is lost (monitored powered off) but when the monitor is powered back on. With the monitor off, I was able to ssh into the box and see the cagebreak process still running and interact with the IPC socket. It was only when powering on the monitor that the session ended and I had to restart cagebreak.

project-repo commented 1 year ago

Hi sodface

Thanks for your insights and kind words!

The observation that Cagebreak continues to run until output reconnection is rather interesting - I was not aware of the fact.

We are currently investigating the feasibility of keeping Cagebreak running and preserving the last output setup if exactly the same output is reconnected. In all other cases all views will be put on workspace 1 in full-screen upon reattaching an output.

Note that the above proposal only concerns disconnecting the last remaining output. For any additional outputs their views will be treated as they are now when the output is removed. This is so that no views are "lost" should a secondary output be accidentally disconnected - Consider an important browser window suddenly lost or hard to retrieve if a monitor fails for example.

This is however still being investigated and will take time to implement.

Do you have any suggestions or refinements?

cheers project-repo

sodface commented 1 year ago

Hi project-repo, some additional information and comments.

The session error also occurs when switching inputs via the monitor menu, eg. cagebreak is connected to the monitor's HDMI port and the monitor also has a VGA input. Switching the monitor to VGA and then back to HDMI will cause the same cagebreak session disconnect as just powering the monitor off and back on.

This is especially problematic in my desktop usage as I use the different inputs on my monitors for various things like connecting my work laptop and working on projects (eg. raspberry pi etc.). If I understand your proposed behavior outlined in the previous comment, in my two monitor desk setup, every time I switch one monitor to a secondary monitor input, my cagebreak views would all move to the remaining active output/monitor? Currently with X (herbstluftwm) I can toggle inputs with no change in window layout, which is my preferred behavior.

I think it would be good if cagebreak just did nothing when outputs come and go. Perhaps a "panic" keybind could bring all views to workspace 1 in full screen when needed?

Below is some debug info when turning power off and then on in a single monitor configuration. Note that the segfault occurs immediately on power on (not on power off). The dmesg log with drm debug info is quite verbose and I just provided a snip from it which shows the cagebreak segfault at [ 5147.124848] and some context before and after.

Thread 1 "cagebreak" received signal SIGSEGV, Segmentation fault.
0x0000555555568188 in view_is_visible (view=view@entry=0xfffffffffffffff0) at ../view.c:66
66      in ../view.c
(gdb) bt
#0  0x0000555555568188 in view_is_visible (view=view@entry=0xfffffffffffffff0) at ../view.c:66
#1  0x00005555555681e5 in view_get_prev_view (view=view@entry=0x7fffeb700890) at ../view.c:39
#2  0x0000555555568317 in view_unmap (view=0x7fffeb700890) at ../view.c:120
#3  0x00007ffff7f53bd5 in wl_signal_emit_mutable () from /usr/lib/libwayland-server.so.0
#4  0x00007ffff7ed528f in unmap_xdg_surface (surface=surface@entry=0x7fffead49910) at ../types/xdg_shell/wlr_xdg_surface.c:36
#5  0x00007ffff7ed5490 in reset_xdg_surface (surface=surface@entry=0x7fffead49910) at ../types/xdg_shell/wlr_xdg_surface.c:399
#6  0x00007ffff7ed558e in xdg_surface_role_destroy (wlr_surface=<optimized out>) at ../types/xdg_shell/wlr_xdg_surface.c:332
#7  0x00007ffff7ed300b in wlr_surface_destroy_role_object (surface=0x7fffe9ea62b0) at ../types/wlr_compositor.c:742
#8  0x00007ffff7ed30c7 in xdg_client_handle_resource_destroy (resource=<optimized out>) at ../types/xdg_shell/wlr_xdg_shell.c:70
#9  0x00007ffff7f54e60 in ?? () from /usr/lib/libwayland-server.so.0
#10 0x00007ffff7f51ac0 in ?? () from /usr/lib/libwayland-server.so.0
#11 0x00007ffff7f55329 in wl_client_destroy () from /usr/lib/libwayland-server.so.0
#12 0x00007ffff7f554b2 in wl_display_destroy_clients () from /usr/lib/libwayland-server.so.0
#13 0x000055555555e300 in main (argc=<optimized out>, argv=<optimized out>) at ../cagebreak.c:690
[ 5147.122317] [drm:__drm_atomic_state_free [drm]] Freeing atomic state 00000000e9cdd5dc                                                                                                       
[ 5147.122431] [drm:drm_ioctl [drm]] comm="cagebreak" pid=2855, dev=0xe201, auth=1, DRM_IOCTL_MODE_DESTROYPROPBLOB                                                                           
[ 5147.122515] [drm:drm_mode_object_put.part.0 [drm]] OBJ ID: 279 (2)                                                                                                                        
[ 5147.122599] [drm:drm_mode_object_put.part.0 [drm]] OBJ ID: 279 (1)                                                                                                                        
[ 5147.122702] [drm:drm_ioctl [drm]] comm="cagebreak" pid=2855, dev=0xe201, auth=1, DRM_IOCTL_MODE_RMFB                                                             
[ 5147.122781] [drm:drm_mode_object_put.part.0 [drm]] OBJ ID: 278 (2)                                                                                                                          
[ 5147.122863] [drm:drm_mode_object_put.part.0 [drm]] OBJ ID: 278 (1)                                                                                                                        
[ 5147.123018] [drm:drm_ioctl [drm]] comm="cagebreak" pid=2855, dev=0xe201, auth=1, DRM_IOCTL_MODE_RMFB                                                                                      
[ 5147.123099] [drm:drm_mode_object_put.part.0 [drm]] OBJ ID: 275 (2)                                                                                                                        
[ 5147.123180] [drm:drm_mode_object_put.part.0 [drm]] OBJ ID: 275 (1)                                                                                               
[ 5147.123322] [drm:drm_ioctl [drm]] comm="cagebreak" pid=2855, dev=0xe201, auth=1, DRM_IOCTL_MODE_GETCONNECTOR                                                                                
[ 5147.123405] [drm:drm_helper_probe_single_connector_modes [drm_kms_helper]] [CONNECTOR:266:DP-3]                                                                                           
[ 5147.123463] i915 0000:00:02.0: [drm:intel_dp_detect [i915]] [CONNECTOR:266:DP-3]                                                                                                          
[ 5147.123699] [drm:drm_helper_probe_single_connector_modes [drm_kms_helper]] [CONNECTOR:266:DP-3] disconnected                                                                              
[ 5147.123754] [drm:drm_mode_object_put.part.0 [drm]] OBJ ID: 266 (2)                                                                                               
[ 5147.123851] [drm:drm_ioctl [drm]] comm="cagebreak" pid=2855, dev=0xe201, auth=1, DRM_IOCTL_MODE_GETCONNECTOR                                                                                
[ 5147.123938] [drm:drm_mode_object_put.part.0 [drm]] OBJ ID: 266 (2)                                                                                                                        
[ 5147.124030] [drm:drm_ioctl [drm]] comm="cagebreak" pid=2855, dev=0xe201, auth=1, DRM_IOCTL_MODE_GETCONNECTOR                                                                              
[ 5147.124107] [drm:drm_helper_probe_single_connector_modes [drm_kms_helper]] [CONNECTOR:272:HDMI-A-3]                                                                                        
[ 5147.124160] i915 0000:00:02.0: [drm:intel_hdmi_detect [i915]] [CONNECTOR:272:HDMI-A-3]                                                                           
[ 5147.124384] [drm:drm_helper_probe_single_connector_modes [drm_kms_helper]] [CONNECTOR:272:HDMI-A-3] disconnected                                                                            
[ 5147.124435] [drm:drm_mode_object_put.part.0 [drm]] OBJ ID: 272 (2)                                                                                                                        
[ 5147.124526] [drm:drm_ioctl [drm]] comm="cagebreak" pid=2855, dev=0xe201, auth=1, DRM_IOCTL_MODE_GETCONNECTOR                                                                              
[ 5147.124609] [drm:drm_mode_object_put.part.0 [drm]] OBJ ID: 272 (2)                                                                                                                        
[ 5147.124848] cagebreak[2855]: segfault at 30 ip 000055a259eb0188 sp 00007ffdd0b63970 error 4 in cagebreak[55a259ea4000+f000]                                      
[ 5147.124879] Code: e8 dd 47 ff ff 48 81 c4 d8 00 00 00 c3 48 8b 47 48 ff 60 10 48 8b 47 28 48 85 c0 74 08 48 39 78 18 74 02 31 c0 c3 48 83 ec 18 <83> 7f 40 01 74 0d e8 df ff ff ff 48 85 c0 
[ 5147.135130] [drm:drm_file_free.part.0 [drm]] comm="cagebreak", pid=2855, dev=0xe280, open_count=4                                                                                         
[ 5147.135263] [drm:drm_file_free.part.0 [drm]] comm="cagebreak", pid=2855, dev=0xe280, open_count=3                                                                                         
[ 5147.135353] [drm:drm_file_free.part.0 [drm]] comm="cagebreak", pid=2855, dev=0xe280, open_count=2                                                                                         
[ 5147.287037] i915 0000:00:02.0: [drm:gen8_de_irq_handler.isra.0 [i915]] hotplug event received, stat 0x00040000, dig 0x00000a88, pins 0x00000040, long 0x00000040 
[ 5147.287237] i915 0000:00:02.0: [drm:intel_hpd_irq_handler [i915]] digital hpd on [ENCODER:255:DDI C (TC)/PHY C] - long                                                                      
[ 5147.287469] i915 0000:00:02.0: [drm:intel_hpd_irq_handler [i915]] Received HPD interrupt on PIN 6 - cnt: 20                                                                               
[ 5147.287734] i915 0000:00:02.0: [drm:intel_dp_hpd_pulse [i915]] got hpd irq on [ENCODER:255:DDI C (TC)/PHY C] - long                                                                       
[ 5147.287998] i915 0000:00:02.0: [drm:i915_hotplug_work_func [i915]] running encoder hotplug functions                                                                                      
[ 5147.288256] i915 0000:00:02.0: [drm:i915_hotplug_work_func [i915]] Connector DP-2 (pin 6) received hotplug event. (retry 0)                                     
[ 5147.288465] i915 0000:00:02.0: [drm:intel_dp_detect [i915]] [CONNECTOR:256:DP-2]                                                                                                            
[ 5147.288688] i915 0000:00:02.0: [drm:__intel_display_power_get_domain.part.0 [i915]] enabling AUX C TC1                  
sodface commented 1 year ago

I rebuilt without Xwayland support and the segfault moved slightly:

Thread 1 "cagebreak" received signal SIGSEGV, Segmentation fault.
view_get_tile (view=view@entry=0xfffffffffffffff0) at ../view.c:56
56      ../view.c: No such file or directory.
(gdb) bt
#0  view_get_tile (view=view@entry=0xfffffffffffffff0) at ../view.c:56
#1  0x0000555555566ed0 in view_is_visible (view=view@entry=0xfffffffffffffff0) at ../view.c:70
#2  0x0000555555566eff in view_get_prev_view (view=view@entry=0x7fffeb6ff4e0) at ../view.c:39
#3  0x0000555555567023 in view_unmap (view=0x7fffeb6ff4e0) at ../view.c:120
#4  0x00007ffff7f53bd5 in wl_signal_emit_mutable () from /usr/lib/libwayland-server.so.0
#5  0x00007ffff7ed528f in unmap_xdg_surface (surface=surface@entry=0x7fffead49910) at ../types/xdg_shell/wlr_xdg_surface.c:36
#6  0x00007ffff7ed5490 in reset_xdg_surface (surface=surface@entry=0x7fffead49910) at ../types/xdg_shell/wlr_xdg_surface.c:399
#7  0x00007ffff7ed558e in xdg_surface_role_destroy (wlr_surface=<optimized out>) at ../types/xdg_shell/wlr_xdg_surface.c:332
#8  0x00007ffff7ed300b in wlr_surface_destroy_role_object (surface=0x7fffe9ea6280) at ../types/wlr_compositor.c:742
#9  0x00007ffff7ed30c7 in xdg_client_handle_resource_destroy (resource=<optimized out>) at ../types/xdg_shell/wlr_xdg_shell.c:70
#10 0x00007ffff7f54e60 in ?? () from /usr/lib/libwayland-server.so.0
#11 0x00007ffff7f51ac0 in ?? () from /usr/lib/libwayland-server.so.0
#12 0x00007ffff7f55329 in wl_client_destroy () from /usr/lib/libwayland-server.so.0
#13 0x00007ffff7f554b2 in wl_display_destroy_clients () from /usr/lib/libwayland-server.so.0
#14 0x000055555555d0e5 in main (argc=<optimized out>, argv=<optimized out>) at ../cagebreak.c:690
project-repo commented 1 year ago

Hi sodface

Thanks a lot for the followup and for the backtrace which is very helpful! Seeing as the segfault occurrs on line 690 of cagebreak.c, this is actually occurring after the event loop has stopped, so this issue should be resolved once we decide on what to do about the handling output removal gracefully. About your proposal with respect to output removal behaviour, I just want to be sure I understand it correctly. You suggest that when an output is removed, cagebreak retains a "memory" of the output and behaves as if it were still there but invisible, right? Isn't that a bit problematic for example when dis- and reattaching several monitors, since cycling outputs will then cycle through many "unreachable" monitors? How would I access or keep track of the views on an "unreachble" output? An alternative that we are currently considering is the following: Instead of having separate workspaces for each output, we would have "global workspaces" and the views on these workspaces would be shared between the outputs (e.g. when cycling). When an output is removed, nothing would change, just as you suggest, the views which were previously on the removed output would simply go back into the pool of "cyclable views". When an output is added back, we could restore the layout of the output. However, this still does not allow one to also restore the views which were focussed on each of the tiles, since it may be possible that these are now visible on another output. Does this explanation make sense? Does anyone have any thoughts on any of this?

cheers project-repo

sodface commented 1 year ago

Hi project-repo,

My two basic use cases I would describe as:

Desktop: Static physical monitor quantity and model with variable state of monitor power and selected input port.

Laptop: Static single builtin monitor with or without a secondary monitor which could vary in model.

All previous testing was done with a desktop form factor thin client (Dell Optiplex 3000) running Alpine Linux and connected to a single HP monitor. Since I had not done any multi-monitor testing, I loaded a spare laptop (Dell 5470) with Alpine and Cagebreak with the intent to test the "Laptop" use case described above.

So far, I have not been able to duplicate the issue. Starting Cagebreak with just the builtin monitor works as expected. After connecting the secondary monitor (the same HP monitor used for previous tests), the output was enabled and I was able to use both monitors for applications. Powering off and changing monitor inputs on the secondary monitor did not cause Cagebreak to exit as it had in previous tests. The builtin monitor continued to display the application that was already visible on it and the secondary output and application stayed available as if the monitor was still present.

Physically disconnecting the cable to the secondary monitor also did not cause the Cagebreak session to crash but did cause the application displayed there to move to workspace 1 on the builtin display. After reconnecting the secondary monitor, the output was enabled and I had to manually move the application to that output to restore the previous layout.

The above behavior is quite nice in my opinion.

About your proposal with respect to output removal behaviour, I just want to be sure I understand it correctly. You suggest that when an output is removed, cagebreak retains a "memory" of the output and behaves as if it were still there but invisible, right?

Perhaps further defining what is meant by output removal is needed? Is this physically disconnecting only or does it include a connected but powered off (mains power applied but monitor turned "off" by power button) monitor or a connected monitor with a different input port selected?

Instead of having separate workspaces for each output, we would have "global workspaces" and the views on these workspaces would be shared between the outputs (e.g. when cycling).

Personally, I prefer the current design of separate workspaces per output.

All testing so far has been with the example Cagebreak configuration. Note that when testing with the desktop, the Optiplex 3000 only has displayport outputs so I was using a DP->HDMI adapter to connect to the monitor. The laptop has an HDMI port so I removed the adapter and connected directly via HMDI->HDMI. Not sure if this is relevant.

What I need to test next is a desktop configuration with two monitors. I thought the laptop test I did this morning would be equivalent to a two monitor desktop configuration but my results seem to differ from what @kinleyd was describing, unless I am completely misreading.

project-repo commented 1 year ago

Hi sodface

Desktop: Static physical monitor quantity and model with variable state of monitor power and selected input port.

Laptop: Static single builtin monitor with or without a secondary monitor which could vary in model.

Yes, I think this is an accurate description of the status quo which kind of clarifies what we are talking about, thanks!

So as of right now cagebreak is optimized for the second use-case, mainly b.c. all involved developers are primarily working on such a setup. The question is now how we should handle the second case.

So far, I have not been able to duplicate the issue.

Are you talking about the issue of cagebreak crashing on output removal? In that case, note that this only occurs when the last output is removed, so as long as you have a builtin monitor which is always running, no problem occurs.

Physically disconnecting the cable to the secondary monitor also did not cause the Cagebreak session to crash but did cause the application displayed there to move to workspace 1 on the builtin display. After reconnecting the secondary monitor, the output was enabled and I had to manually move the application to that output to restore the previous layout.

The above behavior is quite nice in my opinion.

So are you saying that turning off the output did not move the windows over while physically disconnecting it did?

Perhaps further defining what is meant by output removal is needed? Is this physically disconnecting only or does it include a connected but powered off (mains power applied but monitor turned "off" by power button) monitor or a connected monitor with a different input port selected?

Up until now I wasn't sure if we could distinguish between the two cases but if the answer to the above question is affirmative, it appears that we actually should be able to do so...

Personally, I prefer the current design of separate workspaces per output.

Great, thanks for the feedback. We are still evaluating the different possibilities, so any opinion on this topic is very welcome.

Cheers project-repo

sodface commented 1 year ago

note that this only occurs when the last output is removed

Yes, after my laptop testing I suspected that was the case but wasn't sure because kinleyd's original description and follow up comment:

In my use case I have the habit of switching off a monitor sometimes - for example when I'm watching a movie or other video and don't want the distraction of multiple monitors being on. It would be much preferable to be able to just turn the monitor back on and have all the work spaces, windows and tiles still there.

Implies, or at least I read it to mean that they had multiple monitors and would watch a movie on one and turn the other(s) off - so at least one output would remain which would be the one the movie was on. I did not read it to mean that all outputs were removed.

So are you saying that turning off the output did not move the windows over while physically disconnecting it did?

Correct. I tested this a few times to be sure. I even turned the secondary monitor off, and then selected that output via keyboard command and blindly typed a url into the invisible browser. After turning the monitor back on the browser was there and at the correct site, so it was definitely taking keyboard input while the monitor was off. And by off, I mean power still applied, just "off" in the sense of the on/off button, probably more appropriately "stand-by" or "power save".

sodface commented 1 year ago

I tested the desktop, two monitor configuration. It works the same as the laptop if I treat the "primary" monitor (coordinates 0,0) as if it was the laptop builtin monitor, in other words, if I leave that output enabled at all times and only vary the state of the secondary monitor (coordinates 1920,0). I can toggle inputs on the secondary and put it in standby without affecting the primary monitor and without any applications from the secondary moving to the primary. Cagebreak just continues to operate as if the secondary monitor was still visible to the user even though it isn't.

If I do the opposite, change inputs or go to standby on the primary monitor, with the secondary still enabled, things go a little wonky. The applications from the primary move to the secondary (which is now the new primary it seems). When I re-enable what was the primary, it becomes the new secondary and the monitor positions are now incorrect (mouse pointer moves off the right edge of the right hand monitor and wraps around to the left hand monitor).

kinleyd commented 1 year ago

@sodface:

Implies, or at least I read it to mean that they had multiple monitors and would watch a movie on one and turn the other(s) off - so at least one output would remain which would be the one the movie was on. I did not read it to mean that all outputs were removed.

Yes, that is correct, at least one monitor always remained on.

kinleyd commented 1 year ago

And to clarify the last comment: Even with at least one output still active, switching off any other output causes the layout on all monitors to be reset.

project-repo commented 1 year ago

Implies, or at least I read it to mean that they had multiple monitors and would watch a movie on one and turn the other(s) off - so at least one output would remain which would be the one the movie was on. I did not read it to mean that all outputs were removed.

Ah yes, that comment was actually referring to a slightly different issue. To make the thread a bit more transparent I'll give a brief summary of the 2 problems at hand:

  1. Cagebreak shuts down when a monitor is turned off. This happens b.c. Cagebreak dies when it is run on a system with no monitors. However, the tests performed by @sodface seem to indicate that cagebreak only crashes once the monitor is turned on again which seems to imply that we should somehow be able to differentiate between completely removing an output and only pressing the power switch.
  2. What should happen when we turn off a monitor and then turn it on again later (while a second monitor is connected)? Here @kinleyd argues that everything should be left as-is instead of the current behaviour which moves everything to the remaining monitor. This sounds like a good idea but we are still evaluating other options and thinking about how we should do this without potentially "loosing" views on a turned-off output.

If I do the opposite, change inputs or go to standby on the primary monitor, with the secondary still enabled, things go a little wonky. The applications from the primary move to the secondary (which is now the new primary it seems). When I re-enable what was the primary, it becomes the new secondary and the monitor positions are now incorrect (mouse pointer moves off the right edge of the right hand monitor and wraps around to the left hand monitor).

Huh, this is very interesting, I did not expect there to be a difference between "primary" and "secondary" monitors b.c. there is no special handling of the "primary" monitor within cagebreak (or at least there shouldn't be, there apparently is right now). We are looking into this.

Btw., does anyone have any experience in how Sway behaves in this respect?

Cheers project-repo

sodface commented 1 year ago

Btw., does anyone have any experience in how Sway behaves in this respect?

In the dual monitor desktop configuration, Sway and Cagebreak behave mostly the same. The secondary (right hand) monitor can be turned off and on without any change to the layout.

Turning the primary (left hand) monitor off also has no immediate affect. Turning it back on triggers a layout change where all applications from that monitor migrate to the right hand monitor which now becomes 0,0 and the pointer travel to get to the left hand monitor is now incorrect.

The main difference between Sway and Cagebreak is when both monitors are turned off and then turned back on. Cagebreak exits. Sway keeps running and applications migrate to the primary monitor, with the primary monitor being defined by the order in which they are powered back on.

project-repo commented 1 year ago

Good news: We were able to create a setup in which we can reproduce most of the things that you have been reporting here. But based on our experiments, instead of the behaviour being dependent on the location of the output within the cagebreak session, it seems to change based on hardware differences (either HDMI ports or physical monitors). Can anyone confirm this? Based on this observation, we believe that relying on the "destroy" events sent by the monitors is too unreliable and propose instead to create a cagebreak-internal method for temporarily turning off outputs. This would take the shape of a command such as output <ID> dpms off or something of the sort which would turn off the output in question. What do you think?

Cheers project-repo

project-repo commented 1 year ago

Hi everyone, We pushed a new branch feature/disable_outputs yesterday which implements the syntax output <IDENTIFIER> [on|off]. These commands behave similarly to disable and enable with the difference that off does not trigger a change in layout. What do you think? Does this work for your workflows? Cheers, project-repo

sodface commented 1 year ago

Hi project-repo, I missed your comment from two days ago. I've been meaning to come back here and amend my previous findings to note that:

The main difference between Sway and Cagebreak is when both monitors are turned off and then turned back on. Cagebreak exits.

Cagebreak doesn't always exit. It was during my initial testing, but I've noticed that sometimes, like just this morning, I turned both monitors on after being off all night, and my session remained, albeit with rearranged layout.

I will test the new features and report back. Thanks!

sodface commented 1 year ago

Tested and works for me, though I must admit it seems more natural for example to use "2" as the identifier instead of "HDMI-A-2". Wouldn't it be a bit more flexible to use the output_id instead of the output? I think it's a nice feature to have regardless, whether it answers this particular issue... not sure. For me it would be ok, once you know about it. Coming from another WM/compositor and expecting similar behavior when manually turning off monitors maybe not so much? But like I said, I think it's a keeper regardless.

project-repo commented 1 year ago

Hi sodface

thank you for your comments.

We will investigate further regarding the session continuing/discontinuing. We currently think that we will be going ahead with the following plan: When all outputs are disconnected, cagebreak will not crash but instead wait until the next output is reconnected at which point all views will be displayed in fullscreen on desktop 1. If one instead wishes to preserve the layout of the output, one should use the specific cagebreak commands for turning outputs on and off.

Regarding the comment about using numeric output identifiers: this fits into a more general aspiration we've had for some time, namely, having a numeric counterpart for all commands for which this is possible, and will be implemented in due course.

Regarding the compatibility with other compositors/WMs: The truth is, that the only other compositor/WM of which we know the behaviour in these circumstances is sway which as per the comments above seems to behave similarly to cagebreak. What would you exactly want to happen for this to be more consistent?

cheers and sorry for the delayed response project-repo

sodface commented 1 year ago

What would you exactly want to happen for this to be more consistent?

Good point, I suppose "consistency" isn't a good word to use since I wouldn't know how to define it in this context. I guess when I said:

Coming from another WM/compositor and expecting similar behavior when manually turning off monitors maybe not so much?

I was under the impression that kinleyd found Cagebreak's behavior when toggling monitor outputs to be inconsistent from their experience with other WMs (including Xorg based WMs) which is why the issue was opened to begin with. Yes, the session disconnect was the title/focus of the issue but in the subsequent discussion of desired behavior, it seems even absent the session disconnect issue, the layout rearranging on output disconnect may also have prompted an issue to be opened, as that behavior too was inconsistent and undesired.

kinleyd commented 1 year ago

I was under the impression that kinleyd found Cagebreak's behavior when toggling monitor outputs to be inconsistent from their experience with other WMs (including Xorg based WMs) which is why the issue was opened to begin with. Yes, the session disconnect was the title/focus of the issue but in the subsequent discussion of desired behavior, it seems even absent the session disconnect issue, the layout rearranging on output disconnect may also have prompted an issue to be opened, as that behavior too was inconsistent and undesired.

Yes, losing the session or losing the layout when an output is turned off is the problem.

If one instead wishes to preserve the layout of the output, one should use the specific cagebreak commands for turning outputs on and off.

I guess this would work, but again it would not be as natural as just physically hitting the off switch.

project-repo commented 1 year ago

Yes, losing the session or losing the layout when an output is turned off is the problem.

OK, in that case let me pass the question on to you: What exactly would you want to happen when the output is turned off? Keeping the layout when there is another output turned on is most likely not an option, since by the results of our tests and the ones performed by sodface it seems that the "destroy" events sent by the monitors are unreliable, so we'd rather not want to rely on those working correctly. At this point I'm wondering though which WM/compositor you were using when you observed this behaviour when turning on and off monitors: by sodface's comments, it seems that sway behaves similarly to cagebreak, right? In any case, we should be able to ensure that the layout of the last output is preserved when it is turned off and on again, would that be enough? Or do you have any other suggestions what we could do?

Cheers project-repo

sodface commented 1 year ago

Hi everyone, We pushed a new branch feature/disable_outputs yesterday which implements the syntax output <IDENTIFIER> [on|off]. These commands behave similarly to disable and enable with the difference that off does not trigger a change in layout. What do you think? Does this work for your workflows? Cheers, project-repo

I tested this a little more tonight and noticed that in my two monitor desktop config that the output_id of the "primary" monitor changes after cycling it off and then on:

~ $ echo "output HDMI-A-1 off" | nc local:$CAGEBREAK_SOCKET
cg-ipc{"event_name":"configure_output","output":"HDMI-A-1","output_id":1}

~ $ echo "output HDMI-A-1 on" | nc local:$CAGEBREAK_SOCKET
cg-ipc{"event_name":"configure_output","output":"HDMI-A-1","output_id":2}

The layout seems to remain unchanged as my mouse cursor still travels right to left and left to right as expected but since the output_id changes, the ctrl-t 1 and ctrl-t 2 keybinds don't work the same as they did prior to cycling the output.

sodface commented 1 year ago

What exactly would you want to happen when the output is turned off?

I know that question was to kinleyd, but for me, I think good behavior would be:

Keeping the layout when there is another output turned on is most likely not an option

Is the reason for this technical or philosophical?

kinleyd commented 1 year ago

OK, in that case let me pass the question on to you: What exactly would you want to happen when the output is turned off? Keeping the layout when there is another output turned on is most likely not an option, since by the results of our tests and the ones performed by sodface it seems that the "destroy" events sent by the monitors are unreliable, so we'd rather not want to rely on those working correctly. At this point I'm wondering though which WM/compositor you were using when you observed this behaviour when turning on and off monitors: by sodface's comments, it seems that sway behaves similarly to cagebreak, right? In any case, we should be able to ensure that the layout of the last output is preserved when it is turned off and on again, would that be enough? Or do you have any other suggestions what we could do?

Hey, @project-repo, thanks for asking. Basically my view is the same as what @sodface mentioned above. The reason I would want the layout not to be changed if I temporarily turn off an output is that I would simply turn on that output when I needed to use elements configured on it. I think this would be the usual (or a fairly common) approach on a multi-monitor setup where one is inclined to assign work spaces and windows to specific outputs. On my Xorg configuration I use EXWM to do this.

I don't know if this matters but at the moment I'm using just two monitors, one using DP while the other uses a DP -> HDMI connector.

At this point I'm wondering though which WM/compositor you were using when you observed this behaviour when turning on and off monitors

Cagebreak/wlroots

project-repo commented 1 year ago

Following the suggestions given by sodface regarding possible ways forward, we realized that in a way we are talking about 2 distinct intended behaviours (roughly corresponding to the 2 different use-cases described above):

  1. For "fixed" monitors, which are an integral part of the setup (such as laptop screens, the two monitor in sodface's setup, etc.), we would like to have the layout remain as-is when the monitors are turned off b.c. in the end the monitors are still physically present even in they are not detectable by cb.
  2. For "peripheral" monitors, which may or may not be present (such as beamers connected to a laptop, external monitors present at some working areas, etc.), we would like all views to be returned to a visible screen when they are disconnected, since there is no reason to believe that they will ever be added back again (think f.e. disconnecting a beamer at the end of a presentation).

Since these two use-cases are markedly different, one idea that we had following this insight, is that we could provide a command which could inform cagebreak whether a given output is peripheral or fixed so that cagebreak can adjust its behaviour accordingly. Specifically, fixed monitors would always have an output associated with them (from startup), with layout, etc. preserved independently of the physical presence of the monitor. On the other hand, peripheral monitors would come and go depending on the attached hardware, along with their layouts and views. What do you think?

Cheers project-repo

sodface commented 1 year ago

Seems like an excellent solution to me. I meant to clarify above that when I said "disconnect" I meant more of a soft disconnect in that the cable would be connected still, but the monitor had been switched off or had it's input changed. Your proposal sounds like it even accounts for physical disconnects of the cable which is interesting but again, seems like a good solution to me.

project-repo commented 1 year ago

Hi sodface

Yes, the problem with the "soft" vs. "hard" disconnect is that there appears to be no reliable way to distinguish between them on the software side as our (and your) tests show (disconnect signals are sent by the hardware and it appears that they are not particularly reliable). Therefore, the proposed solution was designed to be independent of the specific hardware. Let's wait to hear what @kinleyd says before we start implementing this though.

Oh and I forgot to mention in the previous post: We've taken note of the issue you've been having with output identifiers switching when turning monitors on/off and will take a look at it.

Cheers project-repo

kinleyd commented 1 year ago

@project-repo, that would be the perfect solution! I was fixated on the "fixed" monitors and was surprised there should be any other consideration, until you mentioned the "peripheral" monitors. Thanks and happy coding!

sodface commented 1 year ago

A potential workaround, at least for my main use case which is a desktop with two monitors that I turn "off" via the monitor power buttons (monitors still connected to mains power) and where I always leave the monitor cables connected to the PC:

udevadm control -s

Turn off monitor(s) Time passes Turn on monitor(s)

udevadm control -S

Slight stutter with mouse pointer movement while queued udev events get processed but since the monitors are now on again during the connector reprobe, no layout changes occur.

I haven't tested this extensively but it seems to work, if a bit brute force.

sodface commented 1 year ago

See also:

https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3713

sodface commented 1 year ago

I rebuilt wlroots and basically gutted this function which gets me what I wanted. I can even physically disconnect cables after Cagebreak starts and reconnect and there are no layout changes:

--- ./backend.c
+++ ./backend/drm/backend.c
@@ -143,25 +143,7 @@
 }

 static void handle_dev_change(struct wl_listener *listener, void *data) {
-   struct wlr_drm_backend *drm = wl_container_of(listener, drm, dev_change);
-   struct wlr_device_change_event *change = data;
-
-   if (!drm->session->active) {
-       return;
-   }
-
-   switch (change->type) {
-   case WLR_DEVICE_HOTPLUG:
-       wlr_log(WLR_DEBUG, "Received hotplug event for %s", drm->name);
-       scan_drm_connectors(drm, &change->hotplug);
-       break;
-   case WLR_DEVICE_LEASE:
-       wlr_log(WLR_DEBUG, "Received lease event for %s", drm->name);
-       scan_drm_leases(drm);
-       break;
-   default:
-       wlr_log(WLR_DEBUG, "Received unknown change event for %s", drm->name);
-   }
+   return;
 }

 static void handle_dev_remove(struct wl_listener *listener, void *data) {
sodface commented 1 year ago

Hi project-repo, what do you think about an option in Cagebreak to filter out the drm udev events which seem to be the lower level cause of this issue? This could be toggled as needed at runtime.

It would kind of be the equivalent to the workaround I posted above about using udevadm control to start and stop the event queue, but internal to Cagebreak and limited to drm events instead of all events as with udevadm control.

In the wlroots source a udev event monitor is setup and filtered for "drm". So far as I can tell, libudev doesn't provide a function to mute or change a filter once it's applied. It seems that removing the filter will just result in receiving all events (unfiltered) so that wouldn't have the desired result. Further, I believe the filter has to be set to something and not "" or NULL.

As a quick test, I added the below patch to keybinding.c which removes all filters, adds a new filter, then calls update. Due to the constraints listed above, the new filter is a bogus one ("drm" reversed) which doesn't match any events and so filters out the "drm" change events.

I tacked it onto the end of the time display command. To test, I logged in, opened windows on both monitors and then turned the monitors off then back on, which crashed Cagebreak back to the tty (reproduced the original issue). Then I restarted Cagebreak, opened windows on both monitors again and triggered the time display keybind, which changed the wlroots event filter to "mrd". I could then turn off monitors, switch inputs, disconnect cables etc. with no effect on Cagebreak or window layout.

I've tested this on two different computers, one with two monitors and the other with a single monitor. Both have intel video though and it's the intel video driver that generates the drm uevents, so I'm not sure how other drivers behave.

--- ./keybinding.c
+++ ./keybinding.c
@@ -3,6 +3,7 @@

 #define _POSIX_C_SOURCE 200809L

+#include <libudev.h>
 #include <string.h>
 #include <sys/wait.h>
 #include <unistd.h>
@@ -795,6 +796,11 @@

    message_printf(server->curr_output, "%s", msg);
    free(msg);
+   
+   struct wlr_session *session = wlr_backend_get_session(server->backend);
+   udev_monitor_filter_remove(session->mon);
+   udev_monitor_filter_add_match_subsystem_devtype(session->mon, "mrd", NULL);
+   udev_monitor_filter_update(session->mon);
 }

 struct dyn_str {
project-repo commented 1 year ago

Hi sodface

Thanks for the suggestion and for putting so much effort into this! One question: Assuming that the fixed vs. peripheral monitors setup is implemented soon (sometime this or next week). Would you still be interested in having this suggestion implemented? If so, what benefit does it have over the fixed/peripheral monitors suggestion?

Cheers project-repo

sodface commented 1 year ago

Thanks for the suggestion and for putting so much effort into this!

Apologies for being a bit noisy! It's just been a fun learning exercise for me to try to understand what was happening in the entire "stack", which I concluded is something like:

monitor change -> video driver event -> kernel uevent -> udev event -> wlroots -> Cagebreak

Since this was affecting me daily, I wanted to at least come up with a workaround until the fixes landed here. The question was how and where in the event chain to intervene? The last three seemed best and I did manage to come up with a workaround fix for each, udevadm control at the udev layer and patches at the wlroots and Cagebreak layers.

The wlroots dev said (paraphrasing) that it's up to the compositors to handle these hotplug change events bubbling up from wlroots and as we've seen, at least with Cage, Sway, and Cagebreak, these are handled differently and result in crashing, layout changes etc. depending on the compositor.

I guess that's the right answer, that these hotplug events happen and so the compositor should handle them, but I still think it would be reasonable for wlroots to provide an "opt out" for the user via an environment variable or something, eg. DRM_IGNORE_HOTPLUG=1, because even though sticking your head in the sand may not be the right choice, having a choice is usually better than not.

Assuming that the fixed vs. peripheral monitors setup is implemented soon (sometime this or next week).

Yay!

Would you still be interested in having this suggestion implemented? If so, what benefit does it have over the fixed/peripheral monitors suggestion?

Not sure on this one, I think the fixed/peripheral solution will work great for the majority of situations. I just wasn't sure how far along you were with implementing it, the level of complexity etc. so I thought an option to toggle hotplug events on/off with minimal code/effort might have some appeal.

Would it be correct to assume that monitors present at startup would default to "fixed" and monitors/projectors added after startup would default to "peripheral"? There might be instances where a peripheral monitor is briefly disconnected, either intentionally to re-route the cable or to allow someone else to briefly use the projector/beamer, or unintentionally when someone trips over the cable, where you wouldn't want the peripheral views to immediately return to the fixed monitor. If the new command allows for changing a monitor from peripheral to fixed after startup, that could solve that issue. A command to toggle all hotplug events as I suggested could also be used in this situation. Though at some point you'd want to permanently disconnect so in either case you'd want to reprobe the connectors just in case you'd already disconnected the cable before toggling the command.

project-repo commented 1 year ago

Hi everyone

A proposal for the permanent vs. peripheral monitor setup is available on the feature/permanent_outputs branch for your consideration. We hope to release this feature soon, assuming that the internal tests pass (it may well be that there are still bugs in the implementation atm) and you guys don't have any objections.

The syntax for making an output permanent is as follows:

output <output> permanent

To make it peripheral again, simply replace permanent with peripheral. What do you think? Does this work for your setup?

Cheers project-repo

sodface commented 1 year ago

Thanks, I did a quick check and the behavior is not ideal. Same two permanent monitor desk setup. Login from TTY was fine, both monitors detected correctly and mouse pointer travel was correct from left to right. Dump command listed both outputs in permanent mode.

I opened a terminal on the left monitor and a browser on the right monitor.

I switched off the left monitor then the right monitor, then switched back on the left monitor and then the right monitor. The left monitor just displayed the desktop background color (terminal had disappeared) and the right monitor displayed the browser. Terminal was moved to workspace 1 on the right monitor but not visible with the browser in the foreground. Cursor travel was now incorrect, off the right and wrap around to the left, and the output numbering reversed with the right monitor now output 1.

For the next text I restarted Cagebreak, opened apps again, verified correct pointer travel etc. then physically unplugged the HDMI cables from both monitors and then plugged them back in. Cagebreak had crashed back to the TTY.

For the last test I exited Cagebreak, physically disconnected the HDMI cable from the right monitor and then started Cagebreak. Dump indicated the left monitor was permanent. I then connected the right monitor cable and the monitor was automatically enabled. I was trying to simulate connecting a projector and was expecting the right monitor to default to peripheral mode but dump indicated it too was in permanent mode. Is it intended that the user has to set the output to peripheral even when connecting an output after Cagebreak has started, like you would with a projector most of the time?

Obviously I'm not a developer and this is your project but in my opinion I think a more manual approach would better fit Cagebreak's philosophy and could be used by everyone the same way in every scenario, that is:

That's it.

Want to use a peripheral display / projector? Connect it then trigger the keybind. Finished with the peripheral display? Disconnect it then trigger the keybind.

It's not as convenient but it's one size fits all and seems like it would be simpler to implement.

project-repo commented 1 year ago

Hi sodface

Thank you so much for your feedback! Hmm... I think something is not as it should be. From what you write, it seems that none of your outputs are actually in permanent mode. Did you ever run the output <name> permanent command? You wrote that the output appeared to be in permanent mode out of the box, but this should not be the case, as all outputs are initialized as peripheral. Could you send us the relevant snippet from the dump command? Observe that "permanent: 0" in the dump output means that the output is peripheral, whereas "permanent: 1" means that it is permanent. If you think that is confusing, we could change that though.

Cheers project-repo

PS: Btw., please don't worry about being noisy, all feedback is very much appreciated! Sorry for not getting back sooner on your previous post, we were busy implementing...

sodface commented 1 year ago

Did you ever run the output permanent command?

My apologies! You are correct, I didn't run the command. I saw the word permanent in the dump output and misinterpreted that to mean the outputs defaulted to permanent. Very poor attention to detail on my part.

First test I logged in same as before, two desktop monitors, terminal on the left, browser on the right etc. I entered the new command to set my left monitor, HDMI-A-1, to permanent. As soon as I pressed enter, the terminal I was in on the left monitor disappeared and I was left with the background color on the left and browser on the right. Mouse pointer travel was now incorrect and the right monitor was now output 1. The terminal on the left monitor reappears if you focus the other monitor and then focus back, or just select workspace 1 again. It's almost as if executing the command changed the left monitor to an unused workspace but that can't be correct if cycling focus makes it reappear?

So far, not quite what I expected.

I went ahead and entered the command to make HDMI-A-2 permanent with similar visual results, the browser on the right monitor disappeared and was replaced with the background color, however mouse pointer and output numbering is now back to left(1)->right(2) as desired. Focusing the right monitor, now output 2 again, immediately re-displays the browser.

Both monitors now indicate permanent: 1 in the dump output and layout and mouse pointer travel is as desired. I turned the left monitor off via its' power switch and then back on. After coming back on, the output numbering and mouse travel switched again so it was right(1) -> left(2). Cycling power on the right monitor doesn't correct this, the undesired layout remains unchanged.

Restarting Cagebreak and reversing the command order, eg. make HDMI-A-2 permanent first, then HDMI-A-1, results in no layout changes for the HDMI-A-2 command, but when setting HDMI-A-1, layout changes again to right(1) -> left(2).

I put the commands in the startup config, first in monitor order:

output HDMI-A-1 permanent
output HDMI-A-2 permanent

Which initially seemed ok until I power cycled the left monitor (HDMI-A-1) and then results were the same as above, layout changed to right(1) -> left(2).

Reversing the command order in the config file results in right(1) -> left(2) at login and my terminal which is started by the config is opened on the right monitor instead of the left.

project-repo commented 1 year ago

follow-up question: Let's say you plug in both monitors and set them to permanent, after which you turn off one of the two. What happens if you cycle to the next output? Is there a kind of "hidden output" to which you can cycle? If so, try opening something there and then turning on the monitor again. Is the window you opened visible on the newly attached monitor?

sodface commented 1 year ago

Yes, with a monitor off, I can cycle outputs, open an application and the application is available there after I turn the monitor back on. Still seeing layout / mouse pointer travel issues though if I cycle power on the "primary" monitor.

project-repo commented 1 year ago

So to get things straight: Everything works as expected except for the fact that the layout is changed when the "primary" output is disconnected, right? Or did I miss something in your previous comments?

sodface commented 1 year ago

Yes.

There are two differences that I've noticed between this and blocking the hotplug events as described previously:

  1. The visual discontinuity when changing the output to permanent after Cagebreak starts, eg. the disappearing application until reselecting the workspace or cycling focus to make it reappear.
  2. Physically disconnecting all output cables and then plugging back in when both monitors are set to peripheral results in Cagebreak exiting back to TTY.

// note I edited 2 to change "permanent" to "peripheral", sorry about that. Cagebreak doesn't exit when both monitors are set to permanent and both cables physically disconnect and reconnected.