fix(core): keep auto-fullscreen within the focus rules
4 files · +121 -12
M docs/config.md +2 -1
| @@ -204,7 +204,6 @@ A swipe moves one step at most. Other gestures (pinch, hold, two-finger scroll) | |||
| 204 | 204 | - HDR-aware apps (games, browsers, video players) can show HDR content. Some need HDR turned on in their own settings. X11 apps stay SDR. | |
| 205 | 205 | - The desktop stays SDR, shown at `sdr-brightness` nits. | |
| 206 | 206 | - Fullscreen HDR windows go straight to the display. | |
| 207 | - | - A window that maps with the exact logical size of its output, without asking for fullscreen (a game in borderless-windowed mode), is put fullscreen as if by `toggle-fullscreen`, without being resized. | |
| 208 | 207 | - Screenshots and captures are SDR (tone-mapped). | |
| 209 | 208 | - If NeferWL crashes, the next compositor may show distorted colours until it sets its own colour pipeline. | |
| 210 | 209 | ||
| @@ -261,6 +260,8 @@ bind.XF86MonBrightnessDown = spawn brightnessctl set 5%- | |||
| 261 | 260 | | `scale-up` / `scale-down` | Zoom the display; the scale is saved to the config | | |
| 262 | 261 | | `quit` | Exit NeferWL | | |
| 263 | 262 | ||
| 263 | + | A parentless window whose first committed size is exactly the logical size of the output it is on, without asking for fullscreen (a game in borderless-windowed mode), is put fullscreen as if by `toggle-fullscreen`, without being resized, when it is the focused window of the shown workspace. Later resizes do not. | |
| 264 | + | ||
| 264 | 265 | ### Default binds | |
| 265 | 266 | ||
| 266 | 267 | | Keys | Action | | |
| @@ -204,7 +204,6 @@ A swipe moves one step at most. Other gestures (pinch, hold, two-finger scroll) | |||||
| 204 | - HDR-aware apps (games, browsers, video players) can show HDR content. Some need HDR turned on in their own settings. X11 apps stay SDR. | 204 | - HDR-aware apps (games, browsers, video players) can show HDR content. Some need HDR turned on in their own settings. X11 apps stay SDR. | ||
| 205 | - The desktop stays SDR, shown at `sdr-brightness` nits. | 205 | - The desktop stays SDR, shown at `sdr-brightness` nits. | ||
| 206 | - Fullscreen HDR windows go straight to the display. | 206 | - Fullscreen HDR windows go straight to the display. | ||
| 207 | - | - A window that maps with the exact logical size of its output, without asking for fullscreen (a game in borderless-windowed mode), is put fullscreen as if by `toggle-fullscreen`, without being resized. | |||
| 208 | - Screenshots and captures are SDR (tone-mapped). | 207 | - Screenshots and captures are SDR (tone-mapped). | ||
| 209 | - If NeferWL crashes, the next compositor may show distorted colours until it sets its own colour pipeline. | 208 | - If NeferWL crashes, the next compositor may show distorted colours until it sets its own colour pipeline. | ||
| 210 | 209 | ||||
| @@ -261,6 +260,8 @@ bind.XF86MonBrightnessDown = spawn brightnessctl set 5%- | |||||
| 261 | | `scale-up` / `scale-down` | Zoom the display; the scale is saved to the config | | 260 | | `scale-up` / `scale-down` | Zoom the display; the scale is saved to the config | | ||
| 262 | | `quit` | Exit NeferWL | | 261 | | `quit` | Exit NeferWL | | ||
| 263 | 262 | ||||
| 263 | + | A parentless window whose first committed size is exactly the logical size of the output it is on, without asking for fullscreen (a game in borderless-windowed mode), is put fullscreen as if by `toggle-fullscreen`, without being resized, when it is the focused window of the shown workspace. Later resizes do not. | |||
| 264 | + | ||||
| 264 | ### Default binds | 265 | ### Default binds | ||
| 265 | 266 | ||||
| 266 | | Keys | Action | | 267 | | Keys | Action | | ||
M internal/adapters/wayland/xdg.go +2 -0
| @@ -418,6 +418,8 @@ func (w *window) afterCommit() { | |||
| 418 | 418 | return | |
| 419 | 419 | } | |
| 420 | 420 | if !w.floating { | |
| 421 | + | // Core is authoritative: this prefilter matches any output, and | |
| 422 | + | // core compares with the output the window is on. | |
| 421 | 423 | // Core reads a tile's first size once, to spot a window that maps | |
| 422 | 424 | // at its output's size without asking for fullscreen. | |
| 423 | 425 | if fw, fh := w.size(); !w.sized && fw > 0 && fh > 0 { | |
| @@ -418,6 +418,8 @@ func (w *window) afterCommit() { | |||||
| 418 | return | 418 | return | ||
| 419 | } | 419 | } | ||
| 420 | if !w.floating { | 420 | if !w.floating { | ||
| 421 | + | // Core is authoritative: this prefilter matches any output, and | |||
| 422 | + | // core compares with the output the window is on. | |||
| 421 | // Core reads a tile's first size once, to spot a window that maps | 423 | // Core reads a tile's first size once, to spot a window that maps | ||
| 422 | // at its output's size without asking for fullscreen. | 424 | // at its output's size without asking for fullscreen. | ||
| 423 | if fw, fh := w.size(); !w.sized && fw > 0 && fh > 0 { | 425 | if fw, fh := w.size(); !w.sized && fw > 0 && fh > 0 { | ||
M internal/core/legacy_fullscreen_internal_test.go +94 -0
| @@ -73,3 +73,97 @@ func TestLegacyFullscreenNoFalsePositive(t *testing.T) { | |||
| 73 | 73 | } | |
| 74 | 74 | }) | |
| 75 | 75 | } | |
| 76 | + | ||
| 77 | + | // With animations on, the entrance never restarts for the window that went | |
| 78 | + | // fullscreen: no motion, and the scene shows it at the full output. | |
| 79 | + | func TestLegacyFullscreenDropsEntrance(t *testing.T) { | |
| 80 | + | c, _, _ := fadeCore(t) | |
| 81 | + | c.cfg.Animations.On = true | |
| 82 | + | sc := c.cur() | |
| 83 | + | c.mapWindow(ports.WindowMapped{ID: 1}) | |
| 84 | + | c.stopAnimations() | |
| 85 | + | c.mapWindow(ports.WindowMapped{ID: 2}) | |
| 86 | + | o := sc.rect() | |
| 87 | + | c.resizeFloating(ports.WindowResized{ID: 2, Width: o.W, Height: o.H}) | |
| 88 | + | s := indicatorScene(t, c) | |
| 89 | + | if _, ok := sc.rects[2]; ok { | |
| 90 | + | t.Fatalf("entrance motion left for the fullscreen window: %+v", sc.rects) | |
| 91 | + | } | |
| 92 | + | if w := sceneWindow(t, s, 2); w.Rect != (Rect{W: o.W, H: o.H}) || w.Fade != 0 { | |
| 93 | + | t.Fatalf("scene window %+v, want the full output %+v", w, o) | |
| 94 | + | } | |
| 95 | + | } | |
| 96 | + | ||
| 97 | + | // Focus rules (ADR 011): never takes the focus from a fullscreen cover, nor | |
| 98 | + | // acts on a workspace that is not shown. | |
| 99 | + | func TestLegacyFullscreenRespectsFocus(t *testing.T) { | |
| 100 | + | t.Run("under a fullscreen window", func(t *testing.T) { | |
| 101 | + | c, _, _ := fadeCore(t) | |
| 102 | + | sc := c.cur() | |
| 103 | + | c.mapWindow(ports.WindowMapped{ID: 1}) | |
| 104 | + | sc.mon.SetFullscreen(1, true) | |
| 105 | + | c.mapWindow(ports.WindowMapped{ID: 2}) | |
| 106 | + | o := sc.rect() | |
| 107 | + | c.resizeFloating(ports.WindowResized{ID: 2, Width: o.W, Height: o.H}) | |
| 108 | + | w := sc.mon.Current() | |
| 109 | + | if w.fullscreen != 1 { | |
| 110 | + | t.Fatalf("fullscreen = %d, want the cover 1", w.fullscreen) | |
| 111 | + | } | |
| 112 | + | if id, _ := w.Focused(); id != 1 { | |
| 113 | + | t.Fatalf("focus moved to %d", id) | |
| 114 | + | } | |
| 115 | + | }) | |
| 116 | + | t.Run("other workspace", func(t *testing.T) { | |
| 117 | + | c, _, _ := fadeCore(t) | |
| 118 | + | sc := c.cur() | |
| 119 | + | c.mapWindow(ports.WindowMapped{ID: 1}) | |
| 120 | + | ws := sc.mon.Current() | |
| 121 | + | sc.mon.Focus(1) | |
| 122 | + | if sc.mon.Current() == ws { | |
| 123 | + | t.Skip("no second workspace") | |
| 124 | + | } | |
| 125 | + | o := sc.rect() | |
| 126 | + | c.resizeFloating(ports.WindowResized{ID: 1, Width: o.W, Height: o.H}) | |
| 127 | + | if ws.fullscreen != 0 { | |
| 128 | + | t.Fatal("a window on a hidden workspace went fullscreen") | |
| 129 | + | } | |
| 130 | + | }) | |
| 131 | + | } | |
| 132 | + | ||
| 133 | + | func TestLegacyFullscreenRemapAndOutputs(t *testing.T) { | |
| 134 | + | t.Run("remap", func(t *testing.T) { | |
| 135 | + | c, _, _ := fadeCore(t) | |
| 136 | + | sc := c.cur() | |
| 137 | + | c.mapWindow(ports.WindowMapped{ID: 1, Remap: true}) | |
| 138 | + | o := sc.rect() | |
| 139 | + | c.resizeFloating(ports.WindowResized{ID: 1, Width: o.W, Height: o.H}) | |
| 140 | + | if sc.mon.Current().fullscreen != 0 { | |
| 141 | + | t.Fatal("a remap went fullscreen") | |
| 142 | + | } | |
| 143 | + | }) | |
| 144 | + | second := func(t *testing.T) (*Core, *screen) { | |
| 145 | + | c, _, _ := fadeCore(t) | |
| 146 | + | c.addScreen(ports.OutputInfo{Name: "B", Width: 500, Height: 300}) | |
| 147 | + | c.focusScreen = c.screenIndex("B") | |
| 148 | + | return c, c.cur() | |
| 149 | + | } | |
| 150 | + | t.Run("sized to its own output", func(t *testing.T) { | |
| 151 | + | c, b := second(t) | |
| 152 | + | c.mapWindow(ports.WindowMapped{ID: 1}) | |
| 153 | + | if s, _ := c.screenOf(1); s != b { | |
| 154 | + | t.Fatal("window not on B") | |
| 155 | + | } | |
| 156 | + | c.resizeFloating(ports.WindowResized{ID: 1, Width: 500, Height: 300}) | |
| 157 | + | if b.mon.Current().fullscreen != 1 { | |
| 158 | + | t.Fatal("window sized to its output B did not go fullscreen") | |
| 159 | + | } | |
| 160 | + | }) | |
| 161 | + | t.Run("sized to another output", func(t *testing.T) { | |
| 162 | + | c, b := second(t) | |
| 163 | + | c.mapWindow(ports.WindowMapped{ID: 1}) | |
| 164 | + | c.resizeFloating(ports.WindowResized{ID: 1, Width: 300, Height: 200}) | |
| 165 | + | if b.mon.Current().fullscreen != 0 { | |
| 166 | + | t.Fatal("window sized to output A went fullscreen on B") | |
| 167 | + | } | |
| 168 | + | }) | |
| 169 | + | } | |
| @@ -73,3 +73,97 @@ func TestLegacyFullscreenNoFalsePositive(t *testing.T) { | |||||
| 73 | } | 73 | } | ||
| 74 | }) | 74 | }) | ||
| 75 | } | 75 | } | ||
| 76 | + | ||||
| 77 | + | // With animations on, the entrance never restarts for the window that went | |||
| 78 | + | // fullscreen: no motion, and the scene shows it at the full output. | |||
| 79 | + | func TestLegacyFullscreenDropsEntrance(t *testing.T) { | |||
| 80 | + | c, _, _ := fadeCore(t) | |||
| 81 | + | c.cfg.Animations.On = true | |||
| 82 | + | sc := c.cur() | |||
| 83 | + | c.mapWindow(ports.WindowMapped{ID: 1}) | |||
| 84 | + | c.stopAnimations() | |||
| 85 | + | c.mapWindow(ports.WindowMapped{ID: 2}) | |||
| 86 | + | o := sc.rect() | |||
| 87 | + | c.resizeFloating(ports.WindowResized{ID: 2, Width: o.W, Height: o.H}) | |||
| 88 | + | s := indicatorScene(t, c) | |||
| 89 | + | if _, ok := sc.rects[2]; ok { | |||
| 90 | + | t.Fatalf("entrance motion left for the fullscreen window: %+v", sc.rects) | |||
| 91 | + | } | |||
| 92 | + | if w := sceneWindow(t, s, 2); w.Rect != (Rect{W: o.W, H: o.H}) || w.Fade != 0 { | |||
| 93 | + | t.Fatalf("scene window %+v, want the full output %+v", w, o) | |||
| 94 | + | } | |||
| 95 | + | } | |||
| 96 | + | ||||
| 97 | + | // Focus rules (ADR 011): never takes the focus from a fullscreen cover, nor | |||
| 98 | + | // acts on a workspace that is not shown. | |||
| 99 | + | func TestLegacyFullscreenRespectsFocus(t *testing.T) { | |||
| 100 | + | t.Run("under a fullscreen window", func(t *testing.T) { | |||
| 101 | + | c, _, _ := fadeCore(t) | |||
| 102 | + | sc := c.cur() | |||
| 103 | + | c.mapWindow(ports.WindowMapped{ID: 1}) | |||
| 104 | + | sc.mon.SetFullscreen(1, true) | |||
| 105 | + | c.mapWindow(ports.WindowMapped{ID: 2}) | |||
| 106 | + | o := sc.rect() | |||
| 107 | + | c.resizeFloating(ports.WindowResized{ID: 2, Width: o.W, Height: o.H}) | |||
| 108 | + | w := sc.mon.Current() | |||
| 109 | + | if w.fullscreen != 1 { | |||
| 110 | + | t.Fatalf("fullscreen = %d, want the cover 1", w.fullscreen) | |||
| 111 | + | } | |||
| 112 | + | if id, _ := w.Focused(); id != 1 { | |||
| 113 | + | t.Fatalf("focus moved to %d", id) | |||
| 114 | + | } | |||
| 115 | + | }) | |||
| 116 | + | t.Run("other workspace", func(t *testing.T) { | |||
| 117 | + | c, _, _ := fadeCore(t) | |||
| 118 | + | sc := c.cur() | |||
| 119 | + | c.mapWindow(ports.WindowMapped{ID: 1}) | |||
| 120 | + | ws := sc.mon.Current() | |||
| 121 | + | sc.mon.Focus(1) | |||
| 122 | + | if sc.mon.Current() == ws { | |||
| 123 | + | t.Skip("no second workspace") | |||
| 124 | + | } | |||
| 125 | + | o := sc.rect() | |||
| 126 | + | c.resizeFloating(ports.WindowResized{ID: 1, Width: o.W, Height: o.H}) | |||
| 127 | + | if ws.fullscreen != 0 { | |||
| 128 | + | t.Fatal("a window on a hidden workspace went fullscreen") | |||
| 129 | + | } | |||
| 130 | + | }) | |||
| 131 | + | } | |||
| 132 | + | ||||
| 133 | + | func TestLegacyFullscreenRemapAndOutputs(t *testing.T) { | |||
| 134 | + | t.Run("remap", func(t *testing.T) { | |||
| 135 | + | c, _, _ := fadeCore(t) | |||
| 136 | + | sc := c.cur() | |||
| 137 | + | c.mapWindow(ports.WindowMapped{ID: 1, Remap: true}) | |||
| 138 | + | o := sc.rect() | |||
| 139 | + | c.resizeFloating(ports.WindowResized{ID: 1, Width: o.W, Height: o.H}) | |||
| 140 | + | if sc.mon.Current().fullscreen != 0 { | |||
| 141 | + | t.Fatal("a remap went fullscreen") | |||
| 142 | + | } | |||
| 143 | + | }) | |||
| 144 | + | second := func(t *testing.T) (*Core, *screen) { | |||
| 145 | + | c, _, _ := fadeCore(t) | |||
| 146 | + | c.addScreen(ports.OutputInfo{Name: "B", Width: 500, Height: 300}) | |||
| 147 | + | c.focusScreen = c.screenIndex("B") | |||
| 148 | + | return c, c.cur() | |||
| 149 | + | } | |||
| 150 | + | t.Run("sized to its own output", func(t *testing.T) { | |||
| 151 | + | c, b := second(t) | |||
| 152 | + | c.mapWindow(ports.WindowMapped{ID: 1}) | |||
| 153 | + | if s, _ := c.screenOf(1); s != b { | |||
| 154 | + | t.Fatal("window not on B") | |||
| 155 | + | } | |||
| 156 | + | c.resizeFloating(ports.WindowResized{ID: 1, Width: 500, Height: 300}) | |||
| 157 | + | if b.mon.Current().fullscreen != 1 { | |||
| 158 | + | t.Fatal("window sized to its output B did not go fullscreen") | |||
| 159 | + | } | |||
| 160 | + | }) | |||
| 161 | + | t.Run("sized to another output", func(t *testing.T) { | |||
| 162 | + | c, b := second(t) | |||
| 163 | + | c.mapWindow(ports.WindowMapped{ID: 1}) | |||
| 164 | + | c.resizeFloating(ports.WindowResized{ID: 1, Width: 300, Height: 200}) | |||
| 165 | + | if b.mon.Current().fullscreen != 0 { | |||
| 166 | + | t.Fatal("window sized to output A went fullscreen on B") | |||
| 167 | + | } | |||
| 168 | + | }) | |||
| 169 | + | } | |||
M internal/core/transition.go +23 -11
| @@ -518,7 +518,9 @@ func (c *Core) resizeFloating(v ports.WindowResized) { | |||
| 518 | 518 | old = settledRect(sc.mon, v.ID) | |
| 519 | 519 | } | |
| 520 | 520 | w.ResizeFloating(v.ID, v.Width, v.Height) | |
| 521 | - | c.legacyFullscreen(sc, v) | |
| 521 | + | if c.legacyFullscreen(sc, v) { | |
| 522 | + | return | |
| 523 | + | } | |
| 522 | 524 | if !running { | |
| 523 | 525 | return | |
| 524 | 526 | } | |
| @@ -984,23 +986,33 @@ func movedFrom(b *viewShot, before []viewShot) *viewShot { | |||
| 984 | 986 | } | |
| 985 | 987 | ||
| 986 | 988 | // legacyFullscreen puts a window in the fullscreen state when the first size | |
| 987 | - | // it commits after mapping is exactly its output's logical size: a game in | |
| 988 | - | // borderless-windowed mode (X11 through xwayland-satellite) never asks for | |
| 989 | - | // fullscreen. It is the state toggle-fullscreen sets, and the window is | |
| 990 | - | // not resized. Dialogs (a parent), remaps and later resizes are left alone. | |
| 991 | - | func (c *Core) legacyFullscreen(sc *screen, v ports.WindowResized) { | |
| 989 | + | // it commits after mapping is exactly the logical size of the output it is | |
| 990 | + | // on: a game in borderless-windowed mode (X11 through xwayland-satellite) | |
| 991 | + | // never asks for fullscreen. It is the state toggle-fullscreen sets, and the | |
| 992 | + | // window is not resized. It follows the focus rules (ADR 011): only the | |
| 993 | + | // window that already has the focus on the shown workspace goes fullscreen, | |
| 994 | + | // so a fullscreen cover or another workspace is never disturbed. The parent | |
| 995 | + | // is the one read at map (a later set_parent is not excluded); remaps and | |
| 996 | + | // later resizes are left alone. It reports whether the window went | |
| 997 | + | // fullscreen. | |
| 998 | + | func (c *Core) legacyFullscreen(sc *screen, v ports.WindowResized) bool { | |
| 992 | 999 | if !c.windows.firstSize(v.ID) { | |
| 993 | - | return | |
| 1000 | + | return false | |
| 994 | 1001 | } | |
| 995 | 1002 | if rec := c.windows.lookup(v.ID); rec.client.Parent != 0 || rec.client.Remap { | |
| 996 | - | return | |
| 1003 | + | return false | |
| 997 | 1004 | } | |
| 998 | 1005 | if r := sc.rect(); v.Width != r.W || v.Height != r.H || r.W <= 0 { | |
| 999 | - | return | |
| 1006 | + | return false | |
| 1000 | 1007 | } | |
| 1001 | - | if w, _ := sc.mon.find(v.ID); w != nil { | |
| 1002 | - | w.FocusID(v.ID) | |
| 1008 | + | cur := sc.mon.Current() | |
| 1009 | + | if id, ok := cur.Focused(); !ok || id != v.ID || !cur.has(v.ID) { | |
| 1010 | + | return false | |
| 1003 | 1011 | } | |
| 1004 | 1012 | sc.mon.SetFullscreen(v.ID, true) | |
| 1013 | + | if cur.fullscreen != v.ID { | |
| 1014 | + | return false | |
| 1015 | + | } | |
| 1005 | 1016 | delete(sc.rects, v.ID) | |
| 1017 | + | return true | |
| 1006 | 1018 | } | |
| @@ -518,7 +518,9 @@ func (c *Core) resizeFloating(v ports.WindowResized) { | |||||
| 518 | old = settledRect(sc.mon, v.ID) | 518 | old = settledRect(sc.mon, v.ID) | ||
| 519 | } | 519 | } | ||
| 520 | w.ResizeFloating(v.ID, v.Width, v.Height) | 520 | w.ResizeFloating(v.ID, v.Width, v.Height) | ||
| 521 | - | c.legacyFullscreen(sc, v) | 521 | + | if c.legacyFullscreen(sc, v) { |
| 522 | + | return | |||
| 523 | + | } | |||
| 522 | if !running { | 524 | if !running { | ||
| 523 | return | 525 | return | ||
| 524 | } | 526 | } | ||
| @@ -984,23 +986,33 @@ func movedFrom(b *viewShot, before []viewShot) *viewShot { | |||||
| 984 | } | 986 | } | ||
| 985 | 987 | ||||
| 986 | // legacyFullscreen puts a window in the fullscreen state when the first size | 988 | // legacyFullscreen puts a window in the fullscreen state when the first size | ||
| 987 | - | // it commits after mapping is exactly its output's logical size: a game in | 989 | + | // it commits after mapping is exactly the logical size of the output it is |
| 988 | - | // borderless-windowed mode (X11 through xwayland-satellite) never asks for | 990 | + | // on: a game in borderless-windowed mode (X11 through xwayland-satellite) |
| 989 | - | // fullscreen. It is the state toggle-fullscreen sets, and the window is | 991 | + | // never asks for fullscreen. It is the state toggle-fullscreen sets, and the |
| 990 | - | // not resized. Dialogs (a parent), remaps and later resizes are left alone. | 992 | + | // window is not resized. It follows the focus rules (ADR 011): only the |
| 991 | - | func (c *Core) legacyFullscreen(sc *screen, v ports.WindowResized) { | 993 | + | // window that already has the focus on the shown workspace goes fullscreen, |
| 994 | + | // so a fullscreen cover or another workspace is never disturbed. The parent | |||
| 995 | + | // is the one read at map (a later set_parent is not excluded); remaps and | |||
| 996 | + | // later resizes are left alone. It reports whether the window went | |||
| 997 | + | // fullscreen. | |||
| 998 | + | func (c *Core) legacyFullscreen(sc *screen, v ports.WindowResized) bool { | |||
| 992 | if !c.windows.firstSize(v.ID) { | 999 | if !c.windows.firstSize(v.ID) { | ||
| 993 | - | return | 1000 | + | return false |
| 994 | } | 1001 | } | ||
| 995 | if rec := c.windows.lookup(v.ID); rec.client.Parent != 0 || rec.client.Remap { | 1002 | if rec := c.windows.lookup(v.ID); rec.client.Parent != 0 || rec.client.Remap { | ||
| 996 | - | return | 1003 | + | return false |
| 997 | } | 1004 | } | ||
| 998 | if r := sc.rect(); v.Width != r.W || v.Height != r.H || r.W <= 0 { | 1005 | if r := sc.rect(); v.Width != r.W || v.Height != r.H || r.W <= 0 { | ||
| 999 | - | return | 1006 | + | return false |
| 1000 | } | 1007 | } | ||
| 1001 | - | if w, _ := sc.mon.find(v.ID); w != nil { | 1008 | + | cur := sc.mon.Current() |
| 1002 | - | w.FocusID(v.ID) | 1009 | + | if id, ok := cur.Focused(); !ok || id != v.ID || !cur.has(v.ID) { |
| 1010 | + | return false | |||
| 1003 | } | 1011 | } | ||
| 1004 | sc.mon.SetFullscreen(v.ID, true) | 1012 | sc.mon.SetFullscreen(v.ID, true) | ||
| 1013 | + | if cur.fullscreen != v.ID { | |||
| 1014 | + | return false | |||
| 1015 | + | } | |||
| 1005 | delete(sc.rects, v.ID) | 1016 | delete(sc.rects, v.ID) | ||
| 1017 | + | return true | |||
| 1006 | } | 1018 | } | ||