1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
|
From 1eeeba2aa1be0383ed051e47a64a3a9d526da13f Mon Sep 17 00:00:00 2001
From: rsp4jack <rsp4jack@outlook.com>
Date: Sun, 17 Aug 2025 20:58:28 +0800
Subject: [PATCH 1/3] Squash Merge #2
https://invent.kde.org/automotive/kwin-zones/-/merge_requests/2/
Squashed commit of the following:
commit dcb39f77d286db859648c00bce9946de0b12c065
Author: rsp4jack <rsp4jack@outlook.com>
Date: Sun Aug 17 20:06:55 2025 +0800
Squash merge #2
https://invent.kde.org/automotive/kwin-zones/-/merge_requests/2/
Initial commit
Disclamer
Iteration, add test too
Update protocol to the changes upstream
Allow setting up zones from config files
Update & refactoring
No need to queue when using 6.8
Reorganize client files
Move them into src/ they will be necessary to users.
Comment the keywords thing
Makes it fail building against Qt 6.6 for
some reason
Make constructor private
Better indicates how to use the API
client: Inplement layer_index
Allows setting the layer index for clients and extends the test to make
use of it.
Add README
Allow building without ECM
Improve z-level handling
Use workspace constraints. They are meant for that.
Make sure we don't track items beyond their destruction
Make it a KWin plugin
There's no reason to have the plugin destroy upon graphics reset.
client: Register ZoneItem as an item
compositor: Properly send the entered signals
Improve logging
Use Qt logging categories
Fix client cmake
Revert "Fix client cmake"
This reverts commit c5f541642af62f8fbc717f1e2e213b06858e60e2.
Revert "Allow building without ECM"
This reverts commit 39714ee107032b6fcf0c97fd4819bf7dda4b9331.
Ensure there's licences everywhere
And that they're bundled
Fix build with Qt 6.8.x
Select default output according to spec
Use waylandServer()->display()
Address layers logic
* Fix comparison
* Don't sort a window with itself.
Allow requesting the position from a Q_PROPERTY
It makes for better API.
Include a test for it
client: Ensure we integrate the different properties together with the zone
If an element is integrated later on into the zone, make sure that we
also send the position and the layer.
client: Ensure we initialise items attached to already visible windows
Format the source code automatically
Improve debugging
client: Ensure ZoneItemAttached::zoneChanged gets emitted
client: Ensure we position clients into a zone
compositor: Fix crash on desctruction
Ensure we connect properly
tests: Add a test that takes arguments separately
Useful to test the z-ordering when coming from separate clients
client: add cmake option to build only client qml plugin
compositor: Address issue when having different clients connected
compositor: Reuse logic
compositor: Identify windows as coming from kwin-zones
compositor: Log windows getting positioned
Address outstanding SPDX reuse lint outstanding warnings
Fix build with newer kwin
Keep up with upstream's version of the protocol
Always send the frame extents and position when an item enters a zone
Per the spec, the events sent when an item enters a zone are:
1. Enter
2. Frame extents
3. Position
Check and send the updated frame extents if the geometry changes
Allow negative coordinates relative to the zone
The spec allows items to have negative coordinates relative to the zone, but also allows for compositors to constrain items when necessary. Constrain the item position if it would end up completely outside the zone.
---
src/CMakeLists.txt | 6 +-
src/client/zoneitemattached.cpp | 2 +-
src/client/zonemanager.cpp | 39 +--
src/client/zonemanager.h | 7 +-
src/ext-zones-v1.xml | 451 +++++++++++++++++---------------
src/main.cpp | 10 +-
src/zones.cpp | 295 ++++++++++-----------
src/zones.h | 44 ++++
test/test.qml | 2 +-
9 files changed, 431 insertions(+), 425 deletions(-)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ca9baec..0a04e47 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -12,6 +12,10 @@ if (NOT ONLY_CLIENT_BUILD)
PROTOCOL ext-zones-v1.xml
BASENAME ext-zones-v1
)
+ ecm_add_qtwayland_server_protocol(KWinZones
+ PROTOCOL ${WaylandProtocols_DATADIR}/stable/xdg-shell/xdg-shell.xml
+ BASENAME xdg-shell
+ )
ecm_qt_declare_logging_category(KWinZones
HEADER kwinzonescompositorlogging.h
IDENTIFIER KWINZONES
@@ -20,4 +24,4 @@ if (NOT ONLY_CLIENT_BUILD)
)
target_link_libraries(KWinZones KWin::kwin KF6::ConfigGui)
-endif()
\ No newline at end of file
+endif()
diff --git a/src/client/zoneitemattached.cpp b/src/client/zoneitemattached.cpp
index bdb77c5..94380b0 100644
--- a/src/client/zoneitemattached.cpp
+++ b/src/client/zoneitemattached.cpp
@@ -50,7 +50,7 @@ void ZoneItemAttached::requestPosition(const QPoint& point)
Q_ASSERT(m_item);
qCDebug(KWINZONES_CLIENT) << "requesting in" << zone() << point;
- m_item->requestPosition(point);
+ m_item->set_position(point.x(), point.y());
}
void ZoneItemAttached::setZone(ZoneZone* zone)
diff --git a/src/client/zonemanager.cpp b/src/client/zonemanager.cpp
index 91070fe..d732c39 100644
--- a/src/client/zonemanager.cpp
+++ b/src/client/zonemanager.cpp
@@ -90,10 +90,10 @@ void ZoneItem::initZone()
m_zone->add_item(object());
if (m_layerIndex) {
- m_zone->set_layer(object(), *m_layerIndex);
+ set_layer(*m_layerIndex);
}
if (m_requestedPosition) {
- m_zone->set_position(object(), m_requestedPosition->x(), m_requestedPosition->y());
+ set_position(m_requestedPosition->x(), m_requestedPosition->y());
}
}
@@ -121,7 +121,7 @@ void ZoneItem::setLayerIndex(qint32 layerIndex)
if (m_layerIndex != layerIndex) {
m_layerIndex = layerIndex;
if (object()) {
- zone()->set_layer(object(), layerIndex);
+ set_layer(layerIndex);
}
}
}
@@ -140,7 +140,7 @@ void ZoneItem::requestPosition(const QPoint &point)
}
qCDebug(KWINZONES_CLIENT) << "requesting in" << zone() << "geometry" << point;
- m_zone->set_position(object(), point.x(), point.y());
+ set_position(point.x(), point.y());
}
ZoneItemAttached* ZoneItem::get()
@@ -160,6 +160,11 @@ void ZoneItem::updatePosition(ZoneZone* zone, const QPoint& position)
Q_EMIT positionChanged();
}
+void ZoneItem::ext_zone_item_v1_position_failed()
+{
+ qCDebug(KWINZONES_CLIENT) << "failed to position window" << this;
+}
+
QPoint ZoneItem::position() const
{
return m_pos;
@@ -170,33 +175,7 @@ ZoneZone::ZoneZone(::ext_zone_v1* zone)
{
}
-void ZoneZone::ext_zone_v1_position_failed(ext_zone_item_v1* item)
-{
- if (!item) [[unlikely]] {
- qCDebug(KWINZONES_CLIENT) << "failed to position unknown item";
- return;
- }
- auto www = dynamic_cast<ZoneItem *>(QtWayland::ext_zone_item_v1::fromObject(item));
- qCDebug(KWINZONES_CLIENT) << "failed to position window" << item << www;
-}
-
void ZoneZone::ext_zone_v1_item_entered(ext_zone_item_v1* item)
{
- if (!item) [[unlikely]] {
- qCDebug(KWINZONES_CLIENT) << "unknown item entered";
- return;
- }
qCDebug(KWINZONES_CLIENT) << "item entered" << QtWayland::ext_zone_item_v1::fromObject(item) << item;
- get_position(item);
-}
-
-void ZoneZone::ext_zone_v1_position(ext_zone_item_v1* item, int32_t x, int32_t y)
-{
- if (!item) [[unlikely]] {
- qCDebug(KWINZONES_CLIENT) << "unknown item's position" << x << y;
- return;
- }
- auto www = dynamic_cast<ZoneItem *>(QtWayland::ext_zone_item_v1::fromObject(item));
- Q_ASSERT(www);
- www->updatePosition(this, {x, y});
}
diff --git a/src/client/zonemanager.h b/src/client/zonemanager.h
index b01aaa9..3df179b 100644
--- a/src/client/zonemanager.h
+++ b/src/client/zonemanager.h
@@ -61,6 +61,11 @@ Q_SIGNALS:
void requestedPositionChanged();
private:
+ void ext_zone_item_v1_position(int32_t x, int32_t y) override {
+ updatePosition(m_zone, {x, y});
+ }
+ void ext_zone_item_v1_position_failed() override;
+
void manageSurface();
void initZone();
@@ -89,8 +94,6 @@ private:
void ext_zone_v1_done() override { Q_EMIT done(); }
void ext_zone_v1_item_entered(struct ::ext_zone_item_v1 */*item*/) override;
void ext_zone_v1_item_left(struct ::ext_zone_item_v1 */*item*/) override {}
- void ext_zone_v1_position(struct ::ext_zone_item_v1 *item, int32_t x, int32_t y) override;
- void ext_zone_v1_position_failed(struct ::ext_zone_item_v1 *item) override;
QSize m_size;
QString m_handle;
diff --git a/src/ext-zones-v1.xml b/src/ext-zones-v1.xml
index 7dd0efa..39a39af 100644
--- a/src/ext-zones-v1.xml
+++ b/src/ext-zones-v1.xml
@@ -2,7 +2,8 @@
<protocol name="ext_zones_v1">
<copyright>
- Copyright © 2023-2024 Matthias Klumpp
+ Copyright © 2023-2025 Matthias Klumpp
+ Copyright © 2024-2025 Frank Praznik
Copyright © 2024 Victoria Brekenfeld
Permission is hereby granted, free of charge, to any person obtaining a
@@ -25,12 +26,13 @@
DEALINGS IN THE SOFTWARE.
</copyright>
- <description summary="protocol to manage client-specific zones and set window placement within them">
+ <description summary="protocol to manage client-specific zones for explicit window placement">
This protocol provides a way for clients to create and add toplevel windows
to "zones".
A zone is a isolated environment with its own coordinate space where
- clients can add and arrange windows that logically belong to each other.
+ clients can add and arrange windows that logically belong and relate to
+ each other.
It provides means for, among other things, requesting that windows are
placed at specific coordinates within the zone coordinate space.
See the description of "ext_zone_v1" for more details.
@@ -44,29 +46,9 @@
only be done by creating a new major version of the extension.
</description>
- <interface name="ext_zone_item_v1" version="1">
- <description summary="opaque surface object that can be positioned">
- The zone item object is an opaque descriptor for a positionable
- element, such as a toplevel window.
- It currently can only be created from a 'xdg_toplevel' via a
- 'get_zone_item' request.
- </description>
-
- <request name="destroy" type="destructor">
- <description summary="delete this object">
- Destroys the zone item. This request may be sent at any time by the
- client.
- By destroying the object, the respective item surface remains at its
- last position, but its association with its zone is lost.
- This will also cause it to loose any attached state, like the its
- layer index.
- </description>
- </request>
- </interface>
-
<interface name="ext_zone_manager_v1" version="1">
- <description summary="manage zones for applications">
- The ext_zone_manager interface defines base requests for obtaining and
+ <description summary="manage zones for clients">
+ The 'ext_zone_manager' interface defines base requests for obtaining and
managing zones for a client.
</description>
@@ -78,7 +60,7 @@
<request name="get_zone_item">
<description summary="create a positionable item representing a toplevel">
- Create a new positionable zone item from an xdg_toplevel.
+ Create a new positionable zone item from an 'xdg_toplevel'.
The resulting wrapper object can then be used to position the
toplevel window in a zone.
</description>
@@ -95,19 +77,22 @@
The compositor must keep zone handles valid while any client is
using the corresponding zone and has items associated with it.
- The compositor may always give a client the same zone and
- remember its position and size for the client, but clients should
- not rely on this behavior.
+ The compositor may always give a client the same zone for a given
+ output, and remember its position and size for the client, but
+ clients should not rely on this behavior.
A client can request a zone to be placed on a specific
output by passing a wl_output as 'output'. If a valid output
is set, the compositor should place the zone on that output.
If NULL is passed, the compositor decides the output.
- The client is expected to place its windows in the available space
- the compositor provides for the zone as best as it can.
- The compositor should provide the biggest reasonable space for the
- client, governed by its own policy.
+ The compositor should provide the biggest reasonable zone space
+ for the client, governed by its own policy.
+
+ If the compositor wants to deny zone creation (e.g. on a specific
+ output), the returned zone must be "invalid". A zone is invalid
+ if it has a negative size, in which case the client is forbidden
+ to place items in it.
</description>
<arg name="id" type="new_id" interface="ext_zone_v1"/>
<arg name="output" type="object" interface="wl_output"
@@ -126,15 +111,15 @@
The zone may potentially have been created by a different client.
This allows cooperating clients to share the same coordinate space,
- but prevents other clients from learning window positions of unrelated
- clients.
+ but prevents other clients from e.g. layering their items on top of
+ items of other clients.
If the zone handle was invalid or unknown, a new zone must
be created and returned instead, following the rules outlined
in 'get_zone' and assuming no output preference.
Every new zone object created by this request emits its initial event
- sequence, including the 'handle' event, which my return a different
+ sequence, including the 'handle' event, which must return a different
handle from the one passed to this request in case the existing zone
could not be joined.
</description>
@@ -143,6 +128,160 @@
</request>
</interface>
+ <interface name="ext_zone_item_v1" version="1">
+ <description summary="opaque surface object that can be positioned in a zone">
+ The zone item object is an opaque descriptor for a positionable
+ element, such as a toplevel window.
+ It currently can only be created from an 'xdg_toplevel' via the
+ 'get_zone_item' request on a 'ext_zone_manager'.
+ </description>
+
+ <request name="destroy" type="destructor">
+ <description summary="delete this object">
+ Destroys the zone item. This request may be sent at any time by the
+ client.
+ By destroying the object, the respective item surface remains at its
+ last position, but its association with its zone is lost.
+ This will also cause it to loose any other attached state, like its
+ layer index.
+ </description>
+ </request>
+
+ <event name="frame_extents">
+ <description summary="the extents of the frame bordering the item">
+ The 'frame_extents' event describes the current extents of the frame
+ bordering the item's content area.
+
+ This event is sent immediately after the item joins a zone, or if
+ the item frame extents have been changed by other means (e.g. toggled
+ by a client request, or compositor involvement). The dimensions are in
+ the same coordinate space as the item's zone (the surface coordinate
+ space).
+
+ If the item has no associated frame, the event should still be sent,
+ but extents must be set to zero.
+
+ This event can only be emitted if the item is currently associated
+ with a zone.
+ </description>
+ <arg name="top" type="int" summary="current height of the frame bordering the top of the item"/>
+ <arg name="bottom" type="int" summary="current height of the frame bordering the bottom of the item"/>
+ <arg name="left" type="int" summary="current width of the frame bordering the left of the item"/>
+ <arg name="right" type="int" summary="current width of the frame bordering the right of the item"/>
+ </event>
+
+ <request name="set_position">
+ <description summary="set a preferred item surface position">
+ Request a preferred position (x, y) for the specified item
+ surface to be placed at, relative to its associated zone.
+ This state is double-buffered and is applied on the next
+ wl_surface.commit of the surface represented by 'item'.
+
+ X and Y coordinates are relative to the zone this item is associated
+ with, and must not be larger than the dimensions set by the zone size.
+ They may be smaller than zero, if the item's top-left edge is to be
+ placed beyond the zone's top-left sides, but clients should expect the
+ compositor to more aggressively sanitize the coordinate values in that
+ case.
+ If a coordinate exceeds the zone's maximum bounds, the compositor must
+ sanitize it to more appropriate values (e.g. by clamping the values to
+ the maximum size).
+ For infinite zones, the client may pick any coordinate.
+
+ Compositors implementing this protocol should try to place an item
+ at the requested coordinates relative to the item's zone, unless doing
+ so is not allowed by compositor policy (because e.g. the user has set
+ custom rules for the surface represented by the respective item, the
+ surface overlaps with a protected shell component, session management
+ has loaded previous surface positions or the placement request would
+ send the item out of bounds).
+
+ Clients should be aware that their placement preferences might not
+ always be followed and must be prepared to handle the case where the
+ item is placed at a different position by the compositor.
+
+ Once an item has been mapped, a change to its preferred placement can
+ still be requested and should be applied, but must not be followed
+ by the compositor while the user is interacting with the affected item
+ surface (e.g. clicking & dragging within the window, or resizing it).
+
+ After a call to this request, a 'position' event must be emitted with the
+ item's new actual position.
+ If the current item has no zone associated with it, a 'position_failed'
+ event must be emitted.
+ If the compositor did not move the item at all, not even with sanitized
+ values, a 'position_failed' event must be emitted as well.
+ </description>
+ <arg name="x" type="int" summary="x position relative to zone"/>
+ <arg name="y" type="int" summary="y position relative to zone"/>
+ </request>
+
+ <event name="position">
+ <description summary="notify about the position of an item">
+ This event notifies the client of the current position (x, y) of
+ the item relative to its zone.
+ Coordinates are relative to the zone this item belongs to, and only
+ valid within it.
+ Negative coordinates are possible, if the user has moved an item
+ surface beyone the zone's top-left boundary.
+
+ This event is sent in response to a 'set_position' request,
+ or if the item position has been changed by other means
+ (e.g. user interaction or compositor involvement).
+
+ This event can only be emitted if the item is currently associated
+ with a zone.
+ </description>
+ <arg name="x" type="int" summary="current x position relative to zone"/>
+ <arg name="y" type="int" summary="current y position relative to zone"/>
+ </event>
+
+ <event name="position_failed">
+ <description summary="a set_position request has failed">
+ The compositor was unable to set the position of this item entirely,
+ and could not even find sanitized coordinates to place the item at
+ instead.
+
+ This event will also be emitted if 'set_position' was called while the
+ item had no zone associated with it.
+ </description>
+ </event>
+
+ <request name="set_layer">
+ <description summary="permanently pin an item to a relative z-order">
+ Request a preferred permanent Z position for this item relative to
+ other item surfaces in the same zone this item is associated with.
+ This state is double-buffered and is applied on the next
+ wl_surface.commit of the surface represented by 'item'.
+
+ This function associates a "layer index" with the item, with all
+ item surfaces assumed to be positioned at a layer with index 0 by
+ default.
+ Item surfaces that are positioned in a layer with a higher index
+ permanently float above items with a lower index. Items with a
+ lower layer index sink below items with a higher index.
+
+ Upon user interaction, sunken item surfaces are not raised on top of
+ items in a layer with an index higher than theirs, and floating items
+ do not sink below items in a lower layer, even if these items are
+ selected.
+
+ Items with the same layer index are subject to compositor policy,
+ which usually means they will obey user interaction and raise above or
+ sink below each other depending on which surface is currently activated.
+
+ The layer index only affects the stacking order of items within the
+ same zone. The compositor is allowed to move items of one zone
+ (or no zone) above or below any item in a different zone, regardless
+ of their zone-specific layer index.
+
+ Compositors without support for stacking windows or with other conflicting
+ policy may ignore this request.
+ </description>
+ <arg name="layer_index" type="int" summary="the item surface layer position"/>
+ </request>
+ </interface>
+
<interface name="ext_zone_v1" version="1">
<description summary="area for a client in which in can set window positioning preferences">
An 'ext_zone' describes a display area provided by the compositor in
@@ -153,61 +292,49 @@
restricted elements) or it could be an area of the output the compositor
has specifically chosen for a client to place its surfaces in.
- Windows are added to a zone as 'ext_zone_item_v1' item objects.
-
- A compositor may visually distinguish what makes up a zone, for example
- by adding a frame around it and/or a background.
+ Windows are added to a zone as 'ext_zone_item' objects.
- All items associated with a zone should ideally be always contained
- within it. If possible, the compositor should ensure this by resizing
- zone accordingly if the user moves items surfaces out of the zone's
- original bounds.
-
- All item surface coordinates (x, y) are relative to the selected zone.
+ All item surface position coordinates (x, y) are relative to the selected
+ zone.
They are using the 'size' of the respective zone as coordinate system,
with (0, 0) being in the top left corner.
- An item (e.g. a toplevel window) is considered "in bounds" if its
- top-left coordinate is within the zone's coordinate system (the surface
- may extent beyond the zone's size, but it may not be fully visible to the
- user in that case).
-
If a zone item is moved out of the top/left boundaries of the zone by
user interaction, its coordinates must become negative, relative to the
- zones top-left coordinate origin. A client can not set negative
- coordinates on a zone item (and thereby move it out of the zone).
+ zones top-left coordinate origin. A client may position an item at negative
+ coordinates.
- If a zone item is moved out of a zone's bottom/right boundaries by the
- user or compositor, the compositor should try to resize the zone
- accordingly to make sure all zone items are still contained within it.
- If this can not be done, the compositor must force the item to leave the
- respective zone (emitting an 'item_left' event).
+ The compositor must ensure that any item positioned by the client is
+ visible and accessible to the user, and is not moved into invisible space
+ outside of a zone.
+ Positioning requests may be rejected or altered by the compositor, depending
+ on its policy.
- The absolute position of the zone within the compositor is opaque to the
- client and the compositor may move the entore zone around without the
- client's notice. It may also be arbitrarily resized, in which case the
- respective 'size' event must be emitted again to notify the client.
+ The absolute position of the zone within the compositor's coordinate space
+ is opaque to the client and the compositor may move the entire zone without
+ the client noticing it. A zone may also be arbitrarily resized, in which
+ case the respective 'size' event must be emitted again to notify the client.
Zone items are allowed to have a "layer" attribute, to keep them permanently
above or below other surfaces in the same zone.
Using layers, clients can overlay their own windows permanently, but not
- the ones of other clients not sharing a zone.
+ the ones of other clients not sharing the same zone.
+
+ A zone is always tied to an output and does not extend beyond it.
+
+ A zone may be "invalid". An invalid zone is created with a negative
+ 'size' and must not be used for item arrangement.
Upon creation the compositor must emit 'size' and 'handle' events for the
newly created 'ext_zone', followed by 'done'.
</description>
- <enum name="error">
- <entry name="invalid" summary="a passed value has been invalid"
- value="1"/>
- </enum>
-
<request name="destroy" type="destructor">
<description summary="destroy the ext_zone object">
Using this request a client can tell the compositor that it is not
- going to use the ext_zone object anymore.
+ going to use the 'ext_zone' object anymore.
The zone itself must only be destroyed if no other client
- is using it anymore, so this request may only destroy the object
+ is currently using it, so this request may only destroy the object
reference owned by the client.
</description>
</request>
@@ -223,12 +350,19 @@
If a width or height value is zero, the zone is infinite
in that direction.
+ If the width and height values are negative, the zone is considered
+ "invalid" and must not be used.
+ A size event declaring the zone invalid may only be emitted immediately
+ after the zone was created.
+ A zone must not become invalid at a later time by sending a negative
+ 'size' after the zone has been established.
+
The 'size' event is sent immediately after creating an 'ext_zone_v1',
- and whenever the size of the zone changes. A zone can change at any
- time, for any reason, for example output size or scaling changes,
- or by compositor policy.
+ and whenever the size of the zone changes. A zone size can change at
+ any time, for any reason, for example due to output size or scaling
+ changes, or by compositor policy.
- Upon subsequent emissions of 'size' after 'ext_zone_v1' had already
+ Upon subsequent emissions of 'size' after 'ext_zone' has already
been created, the 'done' event does not have to be sent again.
</description>
<arg name="width" type="int"
@@ -242,7 +376,10 @@
The handle event provides the unique handle of this zone.
The handle may be shared with any client, which then can use it to
join this client's zone by calling
- ext_zone_manager.get_zone.
+ 'ext_zone_manager.get_zone'.
+
+ This event must only be emitted once after the zone was created.
+ If this zone is invalid, the handle must be an empty string.
</description>
<arg name="handle" type="string" summary="the exported zone handle"/>
</event>
@@ -250,18 +387,23 @@
<event name="done">
<description summary="all information about the zone have been sent">
This event is sent after all other properties (size, handle) of an
- ext_zone have been sent.
+ 'ext_zone' have been sent.
This allows changes to the ext_zone properties to be seen as
atomic, even if they happen via multiple events.
</description>
</event>
+ <enum name="error">
+ <entry name="invalid" summary="a passed value has been invalid"
+ value="0"/>
+ </enum>
+
<request name="add_item">
<description summary="associate an item with this zone">
Make 'item' a member of this zone.
This state is double-buffered and is applied on the next
- wl_surface.commit of the surface represented by 'item'.
+ 'wl_surface.commit' of the surface represented by 'item'.
This request associates an item with this zone.
If this request is called on an item that already has a zone
@@ -284,6 +426,16 @@
Compositors might want to prevent zone associations if they
perform specialized window management (e.g. autotiling) that would
make clients moving items between certain zones undesirable.
+
+ Once the 'item' is added to its zone, the compositor must first send
+ a 'frame_extents' event on the item, followed by an initial 'position'
+ event with the item's current position.
+ The compositor must then send 'position' events when the position
+ of the item in its zone is changed, for as long as the item is
+ associated with a zone.
+
+ If the zone is invalid, an 'invalid' error must be raised and the item
+ must not be associated with the invalid zone.
</description>
<arg name="item" type="object" interface="ext_zone_item_v1" summary="the zone item"/>
</request>
@@ -292,7 +444,7 @@
<description summary="disassociate an item from this zone">
Remove 'item' as a member of this zone.
This state is double-buffered and is applied on the next
- wl_surface.commit of the surface represented by 'item'.
+ 'wl_surface.commit' of the surface represented by 'item'.
This request removes the item from this zone explicitly,
making the client unable to retrieve coordinates again.
@@ -310,7 +462,7 @@
This event notifies the client that an item was prevented from
joining this zone.
- It is emitted as a response to 'add_item' if the compoistor did not
+ It is emitted as a response to 'add_item' if the compositor did not
allow the item to join this particular zone.
</description>
<arg name="item" type="object" interface="ext_zone_item_v1" summary="the item that was prevented from joining this zone"/>
@@ -318,7 +470,7 @@
<event name="item_entered">
<description summary="notify about an item having joined this zone">
- This event notifies the client of an item joining a zone.
+ This event notifies the client of an item joining this zone.
It is emitted as a response to 'add_item' or if the compositor
automatically had the item surface (re)join an existing zone.
@@ -332,8 +484,7 @@
therefore the client being unable to retrieve its coordinates in
future.
If the client still wishes to adjust the item surface coordinates, it
- may associate the item with a zone again by calling 'add_item',
- potentially causing the zone boundaries to change.
+ may associate the item with a zone again by calling 'add_item'.
This event is emitted for example if the user moved an item surface out
of a smaller zone's boundaries, or onto a different screen where the
@@ -343,141 +494,7 @@
<arg name="item" type="object" interface="ext_zone_item_v1" summary="the item that has left the zone"/>
</event>
- <request name="set_position">
- <description summary="set a preferred item surface position">
- Request a preferred position (x, y) for the specified item
- surface to be placed at, relative to this zone.
- This state is double-buffered and is applied on the next
- wl_surface.commit of the surface represented by 'item'.
-
- If the item is not yet associated with a zone, or not associated
- with this zone, this request must be discarded and a 'position_failed'
- event must be emitted instead of the 'position' event that is sent on
- success.
-
- X and Y coordinates are relative to this zone, and must not be larger
- than the dimensions set by the zone size, or smaller than zero.
- If any coordinate is negative, and 'invalid' error must be raised.
- If a coordinate exceeds the zone's bounds, the compositor should sanitize
- it to more appropriate values (e.g. by clamping the values to the maximum
- size). For infinite zones, the client may pick any coordinate.
-
- Compositors implementing this protocol should try to place an item
- at the preferred coordinates relative to the zone, unless doing
- so is not allowed by compositor policy because e.g. the user has set
- custom rules for the surface represented by the respective item, the
- surface overlaps with a protected shell component, session management
- has loaded previous surface positions or the placement request would
- send the item out of bounds.
-
- Clients should be aware that their placement preferences might not
- always be followed and must be prepared to handle the case where the
- item is placed at a different position by the compositor.
- The compositor makes the ultimate final placement decision, but should
- take the client's preferences into account.
-
- Once an item has been mapped, a change to its placement preferences
- can still be requested and should be applied, but must not be followed
- by the compositor if the user is interacting with the affected item
- surface (e.g. clicking & dragging within the window, or resizing it).
-
- After a call to this request, a 'position' event must be emitted with the
- item's new actual position.
- </description>
- <arg name="item" type="object" interface="ext_zone_item_v1" summary="the zone item"/>
- <arg name="x" type="int" summary="x position relative to zone"/>
- <arg name="y" type="int" summary="y position relative to zone"/>
- </request>
-
- <request name="get_position">
- <description summary="request the current position of the item">
- Request the position (x, y) of the item, relative to this zone.
-
- If 'item' was not associated with this zone prior to calling
- this request, a 'position_failed' event must be emitted.
- Otherwise, the compositor must respond with a 'position' event
- containing the requested information.
-
- If many 'get_position' requests are send for an item, the
- compositor may still only respond with one 'position' event,
- containing the latest position of the item.
-
- Clients should be aware that the returned position is subject
- to the compositor's window management policies and user settings,
- and may not follow any previously set placement preference.
- The returned position may also not reflect the item's current
- position, as it could have been moved inbetween issueing the request
- and receiving its result.
- </description>
- <arg name="item" type="object" interface="ext_zone_item_v1" summary="the zone item"/>
- </request>
-
- <event name="position">
- <description summary="notify about the position of an item">
- This event notifies the client of the current position (x, y) of
- the item relative to this zone.
- Coordinates are relative to the zone the item belongs to, and only
- valid within it.
- Negative coordinates are possible, if the user has moved an item
- surface beyone the zone's top-left boundary.
-
- The event is only sent in response to a 'get_position' request.
- </description>
- <arg name="item" type="object" interface="ext_zone_item_v1" summary="the item this position belongs to"/>
- <arg name="x" type="int" summary="current x position relative to zone"/>
- <arg name="y" type="int" summary="current y position relative to zone"/>
- </event>
-
- <event name="position_failed">
- <description summary="a get/set position request has failed">
- The compositor was unable to determine the position of the respective
- item, or could not set the position of the item.
-
- This could have happened because a request to 'get_position' was made
- using a zone that the 'item' does not belong to, or if it left its
- zone after a 'get_position' request was made.
-
- This is usually a transient error and the client may attempt a
- 'get_position' request again after verifying the item's placement.
- </description>
- <arg name="item" type="object" interface="ext_zone_item_v1" summary="the item this event belongs to"/>
- </event>
-
- <request name="set_layer">
- <description summary="permanently pin an item to a relative z-order">
- Request a preferred permanent Z position for the specified
- item relative to other item surfaces in this zone.
- This state is double-buffered and is applied on the next
- wl_surface.commit of the surface represented by 'item'.
-
- This function associates a "layer index" with the respective item,
- with all item surfaces assumed to be positioned at a layer with
- index 0 by default.
- Item surfaces that are positioned in a layer with a higher index
- permanently float above items with a lower index. Items with a
- lower layer index sink below items with a higher index.
-
- Upon user interaction, sunken item surfaces are not raised on top of
- items in a layer with an index higher than theirs, and floating items
- do not sink below items in a lower layer, even if these items are
- selected.
-
- Items with the same layer index are subject to compositor policy,
- which usually means they will obey user interaction and raise above or
- sink below each other depending on which surface is currently activated.
-
- The layer index only affects the stacking order of items within the
- same zone. The compositor is allowed to move items of one zone
- (or no zone) above or below any item in a different zone, regardless
- of their zone-specific layer index.
-
- Compositors without support for stacking windows or with other conflicting
- policy may ignore this request.
- </description>
- <arg name="item" type="object" interface="ext_zone_item_v1" summary="the zone item"/>
- <arg name="layer_index" type="int" summary="the item surface layer position"/>
- </request>
-
</interface>
</protocol>
+
diff --git a/src/main.cpp b/src/main.cpp
index cfb93f4..01d1e0f 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -24,15 +24,7 @@ public:
std::unique_ptr<KWin::Plugin> KWinZonesFactory::create() const
{
- switch (kwinApp()->operationMode()) {
- case Application::OperationModeX11:
- return nullptr;
- case Application::OperationModeXwayland:
- case Application::OperationModeWaylandOnly:
- return std::make_unique<Zones>();
- default:
- return nullptr;
- }
+ return std::make_unique<Zones>();
}
#include "main.moc"
diff --git a/src/zones.cpp b/src/zones.cpp
index 4433c56..f7766bf 100644
--- a/src/zones.cpp
+++ b/src/zones.cpp
@@ -5,7 +5,6 @@
*/
#include "zones.h"
-#include "qwayland-server-ext-zones-v1.h"
#include <wayland/clientconnection.h>
#include <wayland/display.h>
@@ -26,17 +25,24 @@
namespace KWin
{
static const int s_version = 1;
-class ExtZoneV1Interface;
+class ExtZoneItemV1Interface;
class ExtZoneItemV1Interface : public QObject, public QtWaylandServer::ext_zone_item_v1
{
Q_OBJECT
public:
- ExtZoneItemV1Interface(XdgToplevelInterface *toplevel)
- : m_toplevel(toplevel)
- {}
+ ExtZoneItemV1Interface(XdgToplevelInterface *toplevel, struct ::wl_client *client, uint32_t id, int version)
+ : ext_zone_item_v1(client, id, version)
+ , m_toplevel(toplevel)
+ {
+ connect(window(), &Window::frameGeometryChanged, this, &ExtZoneItemV1Interface::refreshPosition);
+ }
- ~ExtZoneItemV1Interface();
+ ~ExtZoneItemV1Interface() {
+ if (m_zone) {
+ m_zone->m_items.remove(this);
+ }
+ }
static ExtZoneItemV1Interface *get(::wl_resource *resource)
{
@@ -50,110 +56,54 @@ public:
return waylandServer()->findWindow(m_toplevel->surface());
}
- int layer_index = 0;
- XdgToplevelInterface *const m_toplevel;
- ExtZoneV1Interface* m_zone = nullptr;
-};
-
-class ExtZoneV1Interface : public QObject, public QtWaylandServer::ext_zone_v1
-{
- Q_OBJECT
-public:
- ExtZoneV1Interface(const QRect &area, const QString &handle)
- : m_area(area)
- , m_handle(handle)
- {
- Q_ASSERT(!m_handle.isEmpty());
- setObjectName(handle);
- }
-
- void ext_zone_v1_bind_resource(Resource *resource) override
- {
- const QSizeF size = m_area.size();
- send_size(resource->handle, size.width(), size.height());
- send_handle(resource->handle, m_handle);
- send_done(resource->handle);
- }
-
- void ext_zone_v1_get_position(Resource *resource, struct ::wl_resource *item) override
+ void constrainPosition(QRect &windowRect)
{
- ExtZoneItemV1Interface *zoneItem = ExtZoneItemV1Interface::get(item);
- if (!zoneItem) {
- qCDebug(KWINZONES) << "Zone Item not found" << item;
- return;
+ if (windowRect.left() > m_zone->m_area.right()) {
+ windowRect.moveLeft(m_zone->m_area.right() - windowRect.width());
}
- if (zoneItem->m_zone != this || !zoneItem->m_zone) {
- qCDebug(KWINZONES) << "Item not in the zone" << zoneItem << zoneItem->window()->caption() << zoneItem->m_zone;
- send_position_failed(resource->handle, item);
- return;
+ if (windowRect.right() < m_zone->m_area.left()) {
+ windowRect.moveLeft(m_zone->m_area.left());
}
-
- auto w = zoneItem->window();
- if (!w) {
- qCDebug(KWINZONES) << "Could not find item" << zoneItem->m_toplevel << zoneItem << zoneItem->m_toplevel->surface();
- send_position_failed(resource->handle, item);
- return;
+ if (windowRect.top() > m_zone->m_area.bottom()) {
+ windowRect.moveTop(m_zone->m_area.bottom() - windowRect.height());
+ }
+ if (windowRect.bottom() < m_zone->m_area.top()) {
+ windowRect.moveTop(m_zone->m_area.top());
}
- const QPointF pos = w->frameGeometry().topLeft() - m_area.topLeft();
- send_position(resource->handle, item, pos.x(), pos.y());
}
- void ext_zone_v1_set_position(Resource *resource, struct ::wl_resource *item, int32_t x, int32_t y) override
+
+ void ext_zone_item_v1_set_position(Resource *resource, int32_t x, int32_t y) override
{
- ExtZoneItemV1Interface *zoneWindow = ExtZoneItemV1Interface::get(item);
- if (!zoneWindow) {
- // Can happen when shutting down
- return;
- }
- if (zoneWindow->m_zone != this || !zoneWindow->m_zone) {
- qCDebug(KWINZONES) << "Different zone" << zoneWindow->m_zone << this;
- send_position_failed(resource->handle, item);
+ auto w = window();
+ if (!w || !m_zone) {
+ qCDebug(KWINZONES) << "set_position: Could not find surface" << m_toplevel << m_zone;
+ send_position_failed(resource->handle);
return;
}
- auto w = zoneWindow->window();
- if (!w) {
- qCDebug(KWINZONES) << "Could not find surface" << zoneWindow->m_toplevel;
- send_position_failed(resource->handle, item);
- return;
- }
- const QPoint pos = QPoint(x, y) + m_area.topLeft();
- if (!m_area.contains(pos)) {
- qCDebug(KWINZONES) << "could not position toplevel" << m_area << pos << m_area;
- send_position_failed(resource->handle, item);
- return;
- }
+ const QPoint pos = QPoint(x, y) + m_zone->m_area.topLeft();
+ QRect windowRect = w->frameGeometry().toRect();
+ windowRect.moveTopLeft(QPoint(x, y));
+ constrainPosition(windowRect);
w->setObjectName("kwinzones");
- if (auto s = w->surface()) {
- if (m_setPositionDelay) {
- disconnect(m_setPositionDelay);
- }
-
- m_setPositionDelay = connect(s, &SurfaceInterface::committed, this, [w, pos, handle = zoneWindow->m_zone->m_handle] {
- qCDebug(KWINZONES) << "Setting position. title:" << w->caption() << "zone:" << handle << "position:" << pos << "geometry:" << w->frameGeometry();
- w->move(pos);
- }, Qt::SingleShotConnection);
- }
+ w->move(windowRect.topLeft());
+ qCDebug(KWINZONES) << "Setting position. title:" << w->caption() << "zone:" << m_zone->m_handle << "position:" << pos << "new-position:" << w->frameGeometry();
}
- void ext_zone_v1_set_layer(Resource *resource, struct ::wl_resource *item, int32_t layer_index) override {
- ExtZoneItemV1Interface *zoneWindow = ExtZoneItemV1Interface::get(item);
- if (!zoneWindow) {
- qCDebug(KWINZONES) << "Zone Item not found" << item;
- send_position_failed(resource->handle, item);
- return;
- }
- if (zoneWindow->m_zone != this || !zoneWindow->m_zone) {
- qCDebug(KWINZONES) << "Mismatched zone" << zoneWindow->m_zone;
- send_position_failed(resource->handle, item);
+ void ext_zone_item_v1_set_layer(Resource *resource, int32_t idx) override
+ {
+ Q_ASSERT(m_zone);
+ auto current = window();
+ if (!current || !m_zone) {
+ qCDebug(KWINZONES) << "set_layer: Could not find surface" << m_toplevel << m_zone;
+ send_position_failed(resource->handle);
return;
}
-
- auto current = zoneWindow->window();
current->setObjectName("kwinzones");
- zoneWindow->layer_index = layer_index;
+ layer_index = idx;
StackingUpdatesBlocker blocker(workspace());
- for (auto item : m_items) {
+ for (auto item : m_zone->m_items) {
if (current == item->window()) {
continue;
}
@@ -170,72 +120,30 @@ public:
}
}
- void ext_zone_v1_destroy(Resource *resource) override {
- wl_resource_destroy(resource->handle);
- }
- void ext_zone_v1_add_item(Resource */*resource*/, struct ::wl_resource *item) override {
- setThisZone(item);
- }
- void ext_zone_v1_remove_item(Resource *resource, struct ::wl_resource *item) override {
- auto w = ExtZoneItemV1Interface::get(item);
- if (!w) {
- qCDebug(KWINZONES) << "Zone Item not found" << item;
+ void refreshPosition()
+ {
+ if (!m_zone) {
return;
}
- w->m_zone = nullptr;
- send_item_left(resource->handle, item);
- StackingUpdatesBlocker blocker(workspace());
- for (auto item : m_items) {
- workspace()->unconstrain(w->window(), item->window());
- workspace()->unconstrain(item->window(), w->window());
- }
- m_items.remove(w);
- }
-
- void setArea(const QRect &area) {
- if (m_area == area) {
+ auto w = window();
+ if (!w) {
+ qCWarning(KWINZONES) << "Could not refresh position, could not find the toplevel's window" << m_toplevel->windowTitle() << m_toplevel->windowClass();
return;
}
+ const QPointF pos = w->frameGeometry().topLeft() - m_zone->m_area.topLeft();
+ send_position(pos.x(), pos.y());
- const bool sizeChange = m_area.size() != area.size();
- m_area = area;
- if (sizeChange) {
- const auto clientResources = resourceMap();
- for (auto r : clientResources) {
- send_size(r->handle, m_area.width(), m_area.height());
- }
- }
- }
-
-private:
- void setThisZone(wl_resource *item) {
- auto w = ExtZoneItemV1Interface::get(item);
- if (!w || w->m_zone == this) {
- qCDebug(KWINZONES) << "Skip setting zone" << w << this;
- return;
- }
- if (w->m_zone && w->m_zone != this) {
- for (auto resource : w->m_zone->resourceMap()) {
- if (resource->client() == item->client) {
- w->m_zone->send_item_left(resource->handle, item);
- }
- }
- }
- w->m_zone = this;
- m_items.insert(w);
- for (auto resource : resourceMap()) {
- if (resource->client() == item->client) {
- w->m_zone->send_item_entered(resource->handle, item);
- }
+ const QMargins margins = w->frameMargins();
+ if (margins != m_currentMargins) {
+ m_currentMargins = margins;
+ send_frame_extents(margins.top(), margins.bottom(), margins.left(), margins.right());
}
}
- void addToZone(Window *w, int layer);
- friend class ExtZoneItemV1Interface;
- QSet<ExtZoneItemV1Interface *> m_items;
- QRect m_area;
- const QString m_handle;
- QMetaObject::Connection m_setPositionDelay;
+ int layer_index = 0;
+ XdgToplevelInterface *const m_toplevel;
+ ExtZoneV1Interface* m_zone = nullptr;
+ QMargins m_currentMargins;
};
class ExtZoneManagerV1Interface : public QObject, public QtWaylandServer::ext_zone_manager_v1
@@ -260,15 +168,15 @@ public:
}
auto it = m_zoneWindows.constFind(toplevel);
- if (it == m_zoneWindows.constEnd()) {
- auto zoneWindow = new ExtZoneItemV1Interface(toplevel);
- it = m_zoneWindows.insert(toplevel, zoneWindow);
- connect(toplevel, &XdgToplevelInterface::aboutToBeDestroyed, this, [this, toplevel] {
- auto zoneWindow = m_zoneWindows.take(toplevel);
- delete zoneWindow;
- });
+ if (it != m_zoneWindows.constEnd()) {
+ wl_resource_post_error(resource->handle, QtWaylandServer::ext_zone_v1::error_invalid, "zone item already created");
+ return;
}
- (*it)->add(resource->client(), id, s_version);
+ auto zoneWindow = new ExtZoneItemV1Interface(toplevel, resource->client(), id, s_version);
+ it = m_zoneWindows.insert(toplevel, zoneWindow);
+ connect(toplevel, &XdgToplevelInterface::aboutToBeDestroyed, this, [this, toplevel] {
+ delete m_zoneWindows.take(toplevel);
+ });
}
void ext_zone_manager_v1_get_zone(Resource *resource, uint32_t id, struct ::wl_resource *outputResource) override
@@ -329,15 +237,74 @@ public:
QHash<XdgToplevelInterface *, ExtZoneItemV1Interface *> m_zoneWindows;
};
-Zones::Zones()
- : m_extZones(new ExtZoneManagerV1Interface(waylandServer()->display(), this))
+void ExtZoneV1Interface::ext_zone_v1_remove_item(Resource* resource, wl_resource* item)
{
+ auto w = ExtZoneItemV1Interface::get(item);
+ if (w) {
+ w->m_zone = nullptr;
+ }
+ send_item_left(resource->handle, item);
+ StackingUpdatesBlocker blocker(workspace());
+ for (auto item : m_items) {
+ workspace()->unconstrain(w->window(), item->window());
+ workspace()->unconstrain(item->window(), w->window());
+ }
+ m_items.remove(w);
}
-ExtZoneItemV1Interface::~ExtZoneItemV1Interface() {
- if (m_zone) {
- m_zone->m_items.remove(this);
+void ExtZoneV1Interface::setArea(const QRect& area)
+{
+ if (m_area == area) {
+ return;
+ }
+
+ const bool sizeChange = m_area.size() != area.size();
+ m_area = area;
+ if (sizeChange) {
+ const auto clientResources = resourceMap();
+ for (auto r : clientResources) {
+ send_size(r->handle, m_area.width(), m_area.height());
+ }
+ }
+}
+
+void ExtZoneV1Interface::setThisZone(wl_resource *item)
+{
+ auto w = ExtZoneItemV1Interface::get(item);
+ Q_ASSERT(w);
+ if (w->m_zone == this) {
+ qCDebug(KWINZONES) << "Skip setting zone" << w << this;
+ return;
+ }
+ if (w->m_zone && w->m_zone != this) {
+ for (auto resource : w->m_zone->resourceMap()) {
+ if (resource->client() == item->client) {
+ w->m_zone->send_item_left(resource->handle, item);
+ }
+ }
}
+ w->m_zone = this;
+ m_items.insert(w);
+ for (auto resource : resourceMap()) {
+ if (resource->client() == item->client) {
+ w->m_zone->send_item_entered(resource->handle, item);
+ }
+ }
+
+ auto window = w->window();
+ if (window)
+ {
+ w->m_currentMargins = window->frameMargins();
+ w->send_frame_extents(w->m_currentMargins.top(), w->m_currentMargins.bottom(), w->m_currentMargins.left(), w->m_currentMargins.right());
+
+ const QPointF pos = window->frameGeometry().topLeft() - m_area.topLeft();
+ w->send_position(pos.x(), pos.y());
+ }
+}
+
+Zones::Zones()
+ : m_extZones(new ExtZoneManagerV1Interface(waylandServer()->display(), this))
+{
}
}
diff --git a/src/zones.h b/src/zones.h
index d3d2218..7ae5347 100644
--- a/src/zones.h
+++ b/src/zones.h
@@ -6,11 +6,16 @@
#pragma once
+#include <QRect>
+#include <QSet>
+
#include <plugin.h>
+#include "qwayland-server-ext-zones-v1.h"
namespace KWin
{
class ExtZoneManagerV1Interface;
+class ExtZoneItemV1Interface;
class Zones : public Plugin
{
@@ -22,4 +27,43 @@ private:
ExtZoneManagerV1Interface *const m_extZones;
};
+class ExtZoneV1Interface : public QObject, public QtWaylandServer::ext_zone_v1
+{
+ Q_OBJECT
+public:
+ ExtZoneV1Interface(const QRect &area, const QString &handle)
+ : m_area(area)
+ , m_handle(handle)
+ {
+ Q_ASSERT(!m_handle.isEmpty());
+ setObjectName(handle);
+ }
+
+ void ext_zone_v1_bind_resource(Resource *resource) override
+ {
+ const QSizeF size = m_area.size();
+ send_size(resource->handle, size.width(), size.height());
+ send_handle(resource->handle, m_handle);
+ send_done(resource->handle);
+ }
+
+ void ext_zone_v1_destroy(Resource *resource) override {
+ wl_resource_destroy(resource->handle);
+ }
+ void ext_zone_v1_add_item(Resource */*resource*/, struct ::wl_resource *item) override {
+ setThisZone(item);
+ }
+ void ext_zone_v1_remove_item(Resource *resource, struct ::wl_resource *item) override;
+
+ void setArea(const QRect &area);
+
+private:
+ void setThisZone(wl_resource *item);
+
+ friend class ExtZoneItemV1Interface;
+ QSet<ExtZoneItemV1Interface *> m_items;
+ QRect m_area;
+ const QString m_handle;
+};
+
} // namespace KWin
diff --git a/test/test.qml b/test/test.qml
index a391fe5..76627f2 100644
--- a/test/test.qml
+++ b/test/test.qml
@@ -37,7 +37,7 @@ ApplicationWindow {
angle -= Math.PI * 2
}
const point = Qt.point(centerX + radius * Math.cos(angle) - movingWindow.width / 2,
- centerY + radius * Math.sin(angle) - movingWindow.height / 2);
+ centerY + radius * Math.sin(angle) - movingWindow.height / 2);
movingWindow.ZoneItemAttached.requestPosition(point)
movingWindow.ZoneItemAttached.item.layerIndex = Math.sin((angle * modelData) / rep.model) * rep.model
}
--
2.50.1
|