Changeset View
Changeset View
Standalone View
Standalone View
abstract_client.cpp
Show First 20 Lines • Show All 43 Lines • ▼ Show 20 Line(s) | |||||
44 | #include <KDesktopFile> | 44 | #include <KDesktopFile> | ||
45 | 45 | | |||
46 | #include <QMouseEvent> | 46 | #include <QMouseEvent> | ||
47 | #include <QStyleHints> | 47 | #include <QStyleHints> | ||
48 | 48 | | |||
49 | namespace KWin | 49 | namespace KWin | ||
50 | { | 50 | { | ||
51 | 51 | | |||
52 | static inline int sign(int v) | ||||
53 | { | ||||
54 | return (v > 0) - (v < 0); | ||||
55 | } | ||||
56 | | ||||
52 | QHash<QString, std::weak_ptr<Decoration::DecorationPalette>> AbstractClient::s_palettes; | 57 | QHash<QString, std::weak_ptr<Decoration::DecorationPalette>> AbstractClient::s_palettes; | ||
53 | std::shared_ptr<Decoration::DecorationPalette> AbstractClient::s_defaultPalette; | 58 | std::shared_ptr<Decoration::DecorationPalette> AbstractClient::s_defaultPalette; | ||
54 | 59 | | |||
55 | AbstractClient::AbstractClient() | 60 | AbstractClient::AbstractClient() | ||
56 | : Toplevel() | 61 | : Toplevel() | ||
57 | #ifdef KWIN_BUILD_TABBOX | 62 | #ifdef KWIN_BUILD_TABBOX | ||
58 | , m_tabBoxClient(QSharedPointer<TabBox::TabBoxClientImpl>(new TabBox::TabBoxClientImpl(this))) | 63 | , m_tabBoxClient(QSharedPointer<TabBox::TabBoxClientImpl>(new TabBox::TabBoxClientImpl(this))) | ||
59 | #endif | 64 | #endif | ||
▲ Show 20 Lines • Show All 687 Lines • ▼ Show 20 Line(s) | 751 | { | |||
747 | return rules()->checkMaxSize(QSize(INT_MAX, INT_MAX)); | 752 | return rules()->checkMaxSize(QSize(INT_MAX, INT_MAX)); | ||
748 | } | 753 | } | ||
749 | 754 | | |||
750 | QSize AbstractClient::minSize() const | 755 | QSize AbstractClient::minSize() const | ||
751 | { | 756 | { | ||
752 | return rules()->checkMinSize(QSize(0, 0)); | 757 | return rules()->checkMinSize(QSize(0, 0)); | ||
753 | } | 758 | } | ||
754 | 759 | | |||
760 | void AbstractClient::blockGeometryUpdates(bool block) | ||||
761 | { | ||||
762 | if (block) { | ||||
763 | if (m_blockGeometryUpdates == 0) | ||||
764 | m_pendingGeometryUpdate = PendingGeometryNone; | ||||
765 | ++m_blockGeometryUpdates; | ||||
766 | } else { | ||||
767 | if (--m_blockGeometryUpdates == 0) { | ||||
768 | if (m_pendingGeometryUpdate != PendingGeometryNone) { | ||||
769 | if (isShade()) | ||||
770 | setFrameGeometry(QRect(pos(), adjustedSize()), NormalGeometrySet); | ||||
771 | else | ||||
772 | setFrameGeometry(frameGeometry(), NormalGeometrySet); | ||||
773 | m_pendingGeometryUpdate = PendingGeometryNone; | ||||
774 | } | ||||
775 | } | ||||
776 | } | ||||
777 | } | ||||
778 | | ||||
779 | void AbstractClient::maximize(MaximizeMode m) | ||||
780 | { | ||||
781 | setMaximize(m & MaximizeVertical, m & MaximizeHorizontal); | ||||
782 | } | ||||
783 | | ||||
784 | void AbstractClient::setMaximize(bool vertically, bool horizontally) | ||||
785 | { | ||||
786 | // changeMaximize() flips the state, so change from set->flip | ||||
787 | const MaximizeMode oldMode = maximizeMode(); | ||||
788 | changeMaximize( | ||||
789 | oldMode & MaximizeHorizontal ? !horizontally : horizontally, | ||||
790 | oldMode & MaximizeVertical ? !vertically : vertically, | ||||
791 | false); | ||||
792 | const MaximizeMode newMode = maximizeMode(); | ||||
793 | if (oldMode != newMode) { | ||||
794 | emit clientMaximizedStateChanged(this, newMode); | ||||
795 | emit clientMaximizedStateChanged(this, vertically, horizontally); | ||||
796 | } | ||||
797 | } | ||||
798 | | ||||
799 | void AbstractClient::move(int x, int y, ForceGeometry_t force) | ||||
800 | { | ||||
801 | // resuming geometry updates is handled only in setGeometry() | ||||
802 | Q_ASSERT(pendingGeometryUpdate() == PendingGeometryNone || areGeometryUpdatesBlocked()); | ||||
803 | QPoint p(x, y); | ||||
804 | if (!areGeometryUpdatesBlocked() && p != rules()->checkPosition(p)) { | ||||
805 | qCDebug(KWIN_CORE) << "forced position fail:" << p << ":" << rules()->checkPosition(p); | ||||
806 | } | ||||
807 | if (force == NormalGeometrySet && m_frameGeometry.topLeft() == p) | ||||
808 | return; | ||||
809 | m_frameGeometry.moveTopLeft(p); | ||||
810 | if (areGeometryUpdatesBlocked()) { | ||||
811 | if (pendingGeometryUpdate() == PendingGeometryForced) | ||||
812 | {} // maximum, nothing needed | ||||
813 | else if (force == ForceGeometrySet) | ||||
814 | setPendingGeometryUpdate(PendingGeometryForced); | ||||
815 | else | ||||
816 | setPendingGeometryUpdate(PendingGeometryNormal); | ||||
817 | return; | ||||
818 | } | ||||
819 | doMove(x, y); | ||||
820 | updateWindowRules(Rules::Position); | ||||
821 | screens()->setCurrent(this); | ||||
822 | workspace()->updateStackingOrder(); | ||||
823 | // client itself is not damaged | ||||
824 | addRepaintDuringGeometryUpdates(); | ||||
825 | updateGeometryBeforeUpdateBlocking(); | ||||
826 | emit geometryChanged(); | ||||
827 | } | ||||
828 | | ||||
829 | bool AbstractClient::startMoveResize() | ||||
830 | { | ||||
831 | Q_ASSERT(!isMoveResize()); | ||||
832 | Q_ASSERT(QWidget::keyboardGrabber() == nullptr); | ||||
833 | Q_ASSERT(QWidget::mouseGrabber() == nullptr); | ||||
834 | stopDelayedMoveResize(); | ||||
835 | if (QApplication::activePopupWidget() != nullptr) | ||||
836 | return false; // popups have grab | ||||
837 | if (isFullScreen() && (screens()->count() < 2 || !isMovableAcrossScreens())) | ||||
838 | return false; | ||||
839 | if (!doStartMoveResize()) { | ||||
840 | return false; | ||||
841 | } | ||||
842 | | ||||
843 | invalidateDecorationDoubleClickTimer(); | ||||
844 | | ||||
845 | setMoveResize(true); | ||||
846 | workspace()->setMoveResizeClient(this); | ||||
847 | | ||||
848 | const Position mode = moveResizePointerMode(); | ||||
849 | if (mode != PositionCenter) { // means "isResize()" but moveResizeMode = true is set below | ||||
850 | if (maximizeMode() == MaximizeFull) { // partial is cond. reset in finishMoveResize | ||||
851 | setGeometryRestore(frameGeometry()); // "restore" to current geometry | ||||
852 | setMaximize(false, false); | ||||
853 | } | ||||
854 | } | ||||
855 | | ||||
856 | if (quickTileMode() != QuickTileMode(QuickTileFlag::None) && mode != PositionCenter) { // Cannot use isResize() yet | ||||
857 | // Exit quick tile mode when the user attempts to resize a tiled window | ||||
858 | updateQuickTileMode(QuickTileFlag::None); // Do so without restoring original geometry | ||||
859 | setGeometryRestore(frameGeometry()); | ||||
860 | emit quickTileModeChanged(); | ||||
861 | } | ||||
862 | | ||||
863 | updateHaveResizeEffect(); | ||||
864 | updateInitialMoveResizeGeometry(); | ||||
865 | checkUnrestrictedMoveResize(); | ||||
866 | emit clientStartUserMovedResized(this); | ||||
867 | if (ScreenEdges::self()->isDesktopSwitchingMovingClients()) | ||||
868 | ScreenEdges::self()->reserveDesktopSwitching(true, Qt::Vertical|Qt::Horizontal); | ||||
869 | return true; | ||||
870 | } | ||||
871 | | ||||
872 | void AbstractClient::finishMoveResize(bool cancel) | ||||
873 | { | ||||
874 | GeometryUpdatesBlocker blocker(this); | ||||
875 | const bool wasResize = isResize(); // store across leaveMoveResize | ||||
876 | leaveMoveResize(); | ||||
877 | | ||||
878 | if (cancel) | ||||
879 | setFrameGeometry(initialMoveResizeGeometry()); | ||||
880 | else { | ||||
881 | const QRect &moveResizeGeom = moveResizeGeometry(); | ||||
882 | if (wasResize) { | ||||
883 | const bool restoreH = maximizeMode() == MaximizeHorizontal && | ||||
884 | moveResizeGeom.width() != initialMoveResizeGeometry().width(); | ||||
885 | const bool restoreV = maximizeMode() == MaximizeVertical && | ||||
886 | moveResizeGeom.height() != initialMoveResizeGeometry().height(); | ||||
887 | if (restoreH || restoreV) { | ||||
888 | changeMaximize(restoreH, restoreV, false); | ||||
889 | } | ||||
890 | } | ||||
891 | setFrameGeometry(moveResizeGeom); | ||||
892 | } | ||||
893 | checkScreen(); // needs to be done because clientFinishUserMovedResized has not yet re-activated online alignment | ||||
894 | if (screen() != moveResizeStartScreen()) { | ||||
895 | workspace()->sendClientToScreen(this, screen()); // checks rule validity | ||||
896 | if (maximizeMode() != MaximizeRestore) | ||||
897 | checkWorkspacePosition(); | ||||
898 | } | ||||
899 | | ||||
900 | if (isElectricBorderMaximizing()) { | ||||
901 | setQuickTileMode(electricBorderMode()); | ||||
902 | setElectricBorderMaximizing(false); | ||||
903 | } else if (!cancel) { | ||||
904 | QRect geom_restore = geometryRestore(); | ||||
905 | if (!(maximizeMode() & MaximizeHorizontal)) { | ||||
906 | geom_restore.setX(frameGeometry().x()); | ||||
907 | geom_restore.setWidth(frameGeometry().width()); | ||||
908 | } | ||||
909 | if (!(maximizeMode() & MaximizeVertical)) { | ||||
910 | geom_restore.setY(frameGeometry().y()); | ||||
911 | geom_restore.setHeight(frameGeometry().height()); | ||||
912 | } | ||||
913 | setGeometryRestore(geom_restore); | ||||
914 | } | ||||
915 | // FRAME update(); | ||||
916 | | ||||
917 | emit clientFinishUserMovedResized(this); | ||||
918 | } | ||||
919 | | ||||
920 | // This function checks if it actually makes sense to perform a restricted move/resize. | ||||
921 | // If e.g. the titlebar is already outside of the workarea, there's no point in performing | ||||
922 | // a restricted move resize, because then e.g. resize would also move the window (#74555). | ||||
923 | // NOTE: Most of it is duplicated from handleMoveResize(). | ||||
924 | void AbstractClient::checkUnrestrictedMoveResize() | ||||
925 | { | ||||
926 | if (isUnrestrictedMoveResize()) | ||||
927 | return; | ||||
928 | const QRect &moveResizeGeom = moveResizeGeometry(); | ||||
929 | QRect desktopArea = workspace()->clientArea(WorkArea, moveResizeGeom.center(), desktop()); | ||||
930 | int left_marge, right_marge, top_marge, bottom_marge, titlebar_marge; | ||||
931 | // restricted move/resize - keep at least part of the titlebar always visible | ||||
932 | // how much must remain visible when moved away in that direction | ||||
933 | left_marge = qMin(100 + borderRight(), moveResizeGeom.width()); | ||||
934 | right_marge = qMin(100 + borderLeft(), moveResizeGeom.width()); | ||||
935 | // width/height change with opaque resizing, use the initial ones | ||||
936 | titlebar_marge = initialMoveResizeGeometry().height(); | ||||
937 | top_marge = borderBottom(); | ||||
938 | bottom_marge = borderTop(); | ||||
939 | if (isResize()) { | ||||
940 | if (moveResizeGeom.bottom() < desktopArea.top() + top_marge) | ||||
941 | setUnrestrictedMoveResize(true); | ||||
942 | if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge) | ||||
943 | setUnrestrictedMoveResize(true); | ||||
944 | if (moveResizeGeom.right() < desktopArea.left() + left_marge) | ||||
945 | setUnrestrictedMoveResize(true); | ||||
946 | if (moveResizeGeom.left() > desktopArea.right() - right_marge) | ||||
947 | setUnrestrictedMoveResize(true); | ||||
948 | if (!isUnrestrictedMoveResize() && moveResizeGeom.top() < desktopArea.top()) // titlebar mustn't go out | ||||
949 | setUnrestrictedMoveResize(true); | ||||
950 | } | ||||
951 | if (isMove()) { | ||||
952 | if (moveResizeGeom.bottom() < desktopArea.top() + titlebar_marge - 1) | ||||
953 | setUnrestrictedMoveResize(true); | ||||
954 | // no need to check top_marge, titlebar_marge already handles it | ||||
955 | if (moveResizeGeom.top() > desktopArea.bottom() - bottom_marge + 1) // titlebar mustn't go out | ||||
956 | setUnrestrictedMoveResize(true); | ||||
957 | if (moveResizeGeom.right() < desktopArea.left() + left_marge) | ||||
958 | setUnrestrictedMoveResize(true); | ||||
959 | if (moveResizeGeom.left() > desktopArea.right() - right_marge) | ||||
960 | setUnrestrictedMoveResize(true); | ||||
961 | } | ||||
962 | } | ||||
963 | | ||||
964 | // When the user pressed mouse on the titlebar, don't activate move immediatelly, | ||||
965 | // since it may be just a click. Activate instead after a delay. Move used to be | ||||
966 | // activated only after moving by several pixels, but that looks bad. | ||||
967 | void AbstractClient::startDelayedMoveResize() | ||||
968 | { | ||||
969 | Q_ASSERT(!m_moveResize.delayedTimer); | ||||
970 | m_moveResize.delayedTimer = new QTimer(this); | ||||
971 | m_moveResize.delayedTimer->setSingleShot(true); | ||||
972 | connect(m_moveResize.delayedTimer, &QTimer::timeout, this, | ||||
973 | [this]() { | ||||
974 | Q_ASSERT(isMoveResizePointerButtonDown()); | ||||
975 | if (!startMoveResize()) { | ||||
976 | setMoveResizePointerButtonDown(false); | ||||
977 | } | ||||
978 | updateCursor(); | ||||
979 | stopDelayedMoveResize(); | ||||
980 | } | ||||
981 | ); | ||||
982 | m_moveResize.delayedTimer->start(QApplication::startDragTime()); | ||||
983 | } | ||||
984 | | ||||
985 | void AbstractClient::stopDelayedMoveResize() | ||||
986 | { | ||||
987 | delete m_moveResize.delayedTimer; | ||||
988 | m_moveResize.delayedTimer = nullptr; | ||||
989 | } | ||||
990 | | ||||
755 | void AbstractClient::updateMoveResize(const QPointF ¤tGlobalCursor) | 991 | void AbstractClient::updateMoveResize(const QPointF ¤tGlobalCursor) | ||
756 | { | 992 | { | ||
757 | handleMoveResize(pos(), currentGlobalCursor.toPoint()); | 993 | handleMoveResize(pos(), currentGlobalCursor.toPoint()); | ||
758 | } | 994 | } | ||
759 | 995 | | |||
996 | void AbstractClient::handleMoveResize(const QPoint &local, const QPoint &global) | ||||
997 | { | ||||
998 | const QRect oldGeo = frameGeometry(); | ||||
999 | handleMoveResize(local.x(), local.y(), global.x(), global.y()); | ||||
1000 | if (!isFullScreen() && isMove()) { | ||||
1001 | if (quickTileMode() != QuickTileMode(QuickTileFlag::None) && oldGeo != frameGeometry()) { | ||||
1002 | GeometryUpdatesBlocker blocker(this); | ||||
1003 | setQuickTileMode(QuickTileFlag::None); | ||||
1004 | const QRect &geom_restore = geometryRestore(); | ||||
1005 | setMoveOffset(QPoint(double(moveOffset().x()) / double(oldGeo.width()) * double(geom_restore.width()), | ||||
1006 | double(moveOffset().y()) / double(oldGeo.height()) * double(geom_restore.height()))); | ||||
1007 | if (rules()->checkMaximize(MaximizeRestore) == MaximizeRestore) | ||||
1008 | setMoveResizeGeometry(geom_restore); | ||||
1009 | handleMoveResize(local.x(), local.y(), global.x(), global.y()); // fix position | ||||
1010 | } else if (quickTileMode() == QuickTileMode(QuickTileFlag::None) && isResizable()) { | ||||
1011 | checkQuickTilingMaximizationZones(global.x(), global.y()); | ||||
1012 | } | ||||
1013 | } | ||||
1014 | } | ||||
1015 | | ||||
1016 | void AbstractClient::handleMoveResize(int x, int y, int x_root, int y_root) | ||||
1017 | { | ||||
1018 | if (isWaitingForMoveResizeSync()) | ||||
1019 | return; // we're still waiting for the client or the timeout | ||||
1020 | | ||||
1021 | const Position mode = moveResizePointerMode(); | ||||
1022 | if ((mode == PositionCenter && !isMovableAcrossScreens()) | ||||
1023 | || (mode != PositionCenter && (isShade() || !isResizable()))) | ||||
1024 | return; | ||||
1025 | | ||||
1026 | if (!isMoveResize()) { | ||||
1027 | QPoint p(QPoint(x/* - padding_left*/, y/* - padding_top*/) - moveOffset()); | ||||
1028 | if (p.manhattanLength() >= QApplication::startDragDistance()) { | ||||
1029 | if (!startMoveResize()) { | ||||
1030 | setMoveResizePointerButtonDown(false); | ||||
1031 | updateCursor(); | ||||
1032 | return; | ||||
1033 | } | ||||
1034 | updateCursor(); | ||||
1035 | } else | ||||
1036 | return; | ||||
1037 | } | ||||
1038 | | ||||
1039 | // ShadeHover or ShadeActive, ShadeNormal was already avoided above | ||||
1040 | if (mode != PositionCenter && shadeMode() != ShadeNone) | ||||
1041 | setShade(ShadeNone); | ||||
1042 | | ||||
1043 | QPoint globalPos(x_root, y_root); | ||||
1044 | // these two points limit the geometry rectangle, i.e. if bottomleft resizing is done, | ||||
1045 | // the bottomleft corner should be at is at (topleft.x(), bottomright().y()) | ||||
1046 | QPoint topleft = globalPos - moveOffset(); | ||||
1047 | QPoint bottomright = globalPos + invertedMoveOffset(); | ||||
1048 | QRect previousMoveResizeGeom = moveResizeGeometry(); | ||||
1049 | | ||||
1050 | // TODO move whole group when moving its leader or when the leader is not mapped? | ||||
1051 | | ||||
1052 | auto titleBarRect = [this](bool &transposed, int &requiredPixels) -> QRect { | ||||
1053 | const QRect &moveResizeGeom = moveResizeGeometry(); | ||||
1054 | QRect r(moveResizeGeom); | ||||
1055 | r.moveTopLeft(QPoint(0,0)); | ||||
1056 | switch (titlebarPosition()) { | ||||
1057 | default: | ||||
1058 | case PositionTop: | ||||
1059 | r.setHeight(borderTop()); | ||||
1060 | break; | ||||
1061 | case PositionLeft: | ||||
1062 | r.setWidth(borderLeft()); | ||||
1063 | transposed = true; | ||||
1064 | break; | ||||
1065 | case PositionBottom: | ||||
1066 | r.setTop(r.bottom() - borderBottom()); | ||||
1067 | break; | ||||
1068 | case PositionRight: | ||||
1069 | r.setLeft(r.right() - borderRight()); | ||||
1070 | transposed = true; | ||||
1071 | break; | ||||
1072 | } | ||||
1073 | // When doing a restricted move we must always keep 100px of the titlebar | ||||
1074 | // visible to allow the user to be able to move it again. | ||||
1075 | requiredPixels = qMin(100 * (transposed ? r.width() : r.height()), | ||||
1076 | moveResizeGeom.width() * moveResizeGeom.height()); | ||||
1077 | return r; | ||||
1078 | }; | ||||
1079 | | ||||
1080 | bool update = false; | ||||
1081 | if (isResize()) { | ||||
1082 | QRect orig = initialMoveResizeGeometry(); | ||||
1083 | Sizemode sizemode = SizemodeAny; | ||||
1084 | auto calculateMoveResizeGeom = [this, &topleft, &bottomright, &orig, &sizemode, &mode]() { | ||||
1085 | switch(mode) { | ||||
1086 | case PositionTopLeft: | ||||
1087 | setMoveResizeGeometry(QRect(topleft, orig.bottomRight())); | ||||
1088 | break; | ||||
1089 | case PositionBottomRight: | ||||
1090 | setMoveResizeGeometry(QRect(orig.topLeft(), bottomright)); | ||||
1091 | break; | ||||
1092 | case PositionBottomLeft: | ||||
1093 | setMoveResizeGeometry(QRect(QPoint(topleft.x(), orig.y()), QPoint(orig.right(), bottomright.y()))); | ||||
1094 | break; | ||||
1095 | case PositionTopRight: | ||||
1096 | setMoveResizeGeometry(QRect(QPoint(orig.x(), topleft.y()), QPoint(bottomright.x(), orig.bottom()))); | ||||
1097 | break; | ||||
1098 | case PositionTop: | ||||
1099 | setMoveResizeGeometry(QRect(QPoint(orig.left(), topleft.y()), orig.bottomRight())); | ||||
1100 | sizemode = SizemodeFixedH; // try not to affect height | ||||
1101 | break; | ||||
1102 | case PositionBottom: | ||||
1103 | setMoveResizeGeometry(QRect(orig.topLeft(), QPoint(orig.right(), bottomright.y()))); | ||||
1104 | sizemode = SizemodeFixedH; | ||||
1105 | break; | ||||
1106 | case PositionLeft: | ||||
1107 | setMoveResizeGeometry(QRect(QPoint(topleft.x(), orig.top()), orig.bottomRight())); | ||||
1108 | sizemode = SizemodeFixedW; | ||||
1109 | break; | ||||
1110 | case PositionRight: | ||||
1111 | setMoveResizeGeometry(QRect(orig.topLeft(), QPoint(bottomright.x(), orig.bottom()))); | ||||
1112 | sizemode = SizemodeFixedW; | ||||
1113 | break; | ||||
1114 | case PositionCenter: | ||||
1115 | default: | ||||
1116 | abort(); | ||||
1117 | break; | ||||
1118 | } | ||||
1119 | }; | ||||
1120 | | ||||
1121 | // first resize (without checking constrains), then snap, then check bounds, then check constrains | ||||
1122 | calculateMoveResizeGeom(); | ||||
1123 | // adjust new size to snap to other windows/borders | ||||
1124 | setMoveResizeGeometry(workspace()->adjustClientSize(this, moveResizeGeometry(), mode)); | ||||
1125 | | ||||
1126 | if (!isUnrestrictedMoveResize()) { | ||||
1127 | // Make sure the titlebar isn't behind a restricted area. We don't need to restrict | ||||
1128 | // the other directions. If not visible enough, move the window to the closest valid | ||||
1129 | // point. We bruteforce this by slowly moving the window back to its previous position | ||||
1130 | QRegion availableArea(workspace()->clientArea(FullArea, -1, 0)); // On the screen | ||||
1131 | availableArea -= workspace()->restrictedMoveArea(desktop()); // Strut areas | ||||
1132 | bool transposed = false; | ||||
1133 | int requiredPixels; | ||||
1134 | QRect bTitleRect = titleBarRect(transposed, requiredPixels); | ||||
1135 | int lastVisiblePixels = -1; | ||||
1136 | QRect lastTry = moveResizeGeometry(); | ||||
1137 | bool titleFailed = false; | ||||
1138 | for (;;) { | ||||
1139 | const QRect titleRect(bTitleRect.translated(moveResizeGeometry().topLeft())); | ||||
1140 | int visiblePixels = 0; | ||||
1141 | int realVisiblePixels = 0; | ||||
1142 | for (const QRect &rect : availableArea) { | ||||
1143 | const QRect r = rect & titleRect; | ||||
1144 | realVisiblePixels += r.width() * r.height(); | ||||
1145 | if ((transposed && r.width() == titleRect.width()) || // Only the full size regions... | ||||
1146 | (!transposed && r.height() == titleRect.height())) // ...prevents long slim areas | ||||
1147 | visiblePixels += r.width() * r.height(); | ||||
1148 | } | ||||
1149 | | ||||
1150 | if (visiblePixels >= requiredPixels) | ||||
1151 | break; // We have reached a valid position | ||||
1152 | | ||||
1153 | if (realVisiblePixels <= lastVisiblePixels) { | ||||
1154 | if (titleFailed && realVisiblePixels < lastVisiblePixels) | ||||
1155 | break; // we won't become better | ||||
1156 | else { | ||||
1157 | if (!titleFailed) | ||||
1158 | setMoveResizeGeometry(lastTry); | ||||
1159 | titleFailed = true; | ||||
1160 | } | ||||
1161 | } | ||||
1162 | lastVisiblePixels = realVisiblePixels; | ||||
1163 | QRect moveResizeGeom = moveResizeGeometry(); | ||||
1164 | lastTry = moveResizeGeom; | ||||
1165 | | ||||
1166 | // Not visible enough, move the window to the closest valid point. We bruteforce | ||||
1167 | // this by slowly moving the window back to its previous position. | ||||
1168 | // The geometry changes at up to two edges, the one with the title (if) shall take | ||||
1169 | // precedence. The opposing edge has no impact on visiblePixels and only one of | ||||
1170 | // the adjacent can alter at a time, ie. it's enough to ignore adjacent edges | ||||
1171 | // if the title edge altered | ||||
1172 | bool leftChanged = previousMoveResizeGeom.left() != moveResizeGeom.left(); | ||||
1173 | bool rightChanged = previousMoveResizeGeom.right() != moveResizeGeom.right(); | ||||
1174 | bool topChanged = previousMoveResizeGeom.top() != moveResizeGeom.top(); | ||||
1175 | bool btmChanged = previousMoveResizeGeom.bottom() != moveResizeGeom.bottom(); | ||||
1176 | auto fixChangedState = [titleFailed](bool &major, bool &counter, bool &ad1, bool &ad2) { | ||||
1177 | counter = false; | ||||
1178 | if (titleFailed) | ||||
1179 | major = false; | ||||
1180 | if (major) | ||||
1181 | ad1 = ad2 = false; | ||||
1182 | }; | ||||
1183 | switch (titlebarPosition()) { | ||||
1184 | default: | ||||
1185 | case PositionTop: | ||||
1186 | fixChangedState(topChanged, btmChanged, leftChanged, rightChanged); | ||||
1187 | break; | ||||
1188 | case PositionLeft: | ||||
1189 | fixChangedState(leftChanged, rightChanged, topChanged, btmChanged); | ||||
1190 | break; | ||||
1191 | case PositionBottom: | ||||
1192 | fixChangedState(btmChanged, topChanged, leftChanged, rightChanged); | ||||
1193 | break; | ||||
1194 | case PositionRight: | ||||
1195 | fixChangedState(rightChanged, leftChanged, topChanged, btmChanged); | ||||
1196 | break; | ||||
1197 | } | ||||
1198 | if (topChanged) | ||||
1199 | moveResizeGeom.setTop(moveResizeGeom.y() + sign(previousMoveResizeGeom.y() - moveResizeGeom.y())); | ||||
1200 | else if (leftChanged) | ||||
1201 | moveResizeGeom.setLeft(moveResizeGeom.x() + sign(previousMoveResizeGeom.x() - moveResizeGeom.x())); | ||||
1202 | else if (btmChanged) | ||||
1203 | moveResizeGeom.setBottom(moveResizeGeom.bottom() + sign(previousMoveResizeGeom.bottom() - moveResizeGeom.bottom())); | ||||
1204 | else if (rightChanged) | ||||
1205 | moveResizeGeom.setRight(moveResizeGeom.right() + sign(previousMoveResizeGeom.right() - moveResizeGeom.right())); | ||||
1206 | else | ||||
1207 | break; // no position changed - that's certainly not good | ||||
1208 | setMoveResizeGeometry(moveResizeGeom); | ||||
1209 | } | ||||
1210 | } | ||||
1211 | | ||||
1212 | // Always obey size hints, even when in "unrestricted" mode | ||||
1213 | QSize size = adjustedSize(moveResizeGeometry().size(), sizemode); | ||||
1214 | // the new topleft and bottomright corners (after checking size constrains), if they'll be needed | ||||
1215 | topleft = QPoint(moveResizeGeometry().right() - size.width() + 1, moveResizeGeometry().bottom() - size.height() + 1); | ||||
1216 | bottomright = QPoint(moveResizeGeometry().left() + size.width() - 1, moveResizeGeometry().top() + size.height() - 1); | ||||
1217 | orig = moveResizeGeometry(); | ||||
1218 | | ||||
1219 | // if aspect ratios are specified, both dimensions may change. | ||||
1220 | // Therefore grow to the right/bottom if needed. | ||||
1221 | // TODO it should probably obey gravity rather than always using right/bottom ? | ||||
1222 | if (sizemode == SizemodeFixedH) | ||||
1223 | orig.setRight(bottomright.x()); | ||||
1224 | else if (sizemode == SizemodeFixedW) | ||||
1225 | orig.setBottom(bottomright.y()); | ||||
1226 | | ||||
1227 | calculateMoveResizeGeom(); | ||||
1228 | | ||||
1229 | if (moveResizeGeometry().size() != previousMoveResizeGeom.size()) | ||||
1230 | update = true; | ||||
1231 | } else if (isMove()) { | ||||
1232 | Q_ASSERT(mode == PositionCenter); | ||||
1233 | if (!isMovable()) { // isMovableAcrossScreens() must have been true to get here | ||||
1234 | // Special moving of maximized windows on Xinerama screens | ||||
1235 | int screen = screens()->number(globalPos); | ||||
1236 | if (isFullScreen()) | ||||
1237 | setMoveResizeGeometry(workspace()->clientArea(FullScreenArea, screen, 0)); | ||||
1238 | else { | ||||
1239 | QRect moveResizeGeom = workspace()->clientArea(MaximizeArea, screen, 0); | ||||
1240 | QSize adjSize = adjustedSize(moveResizeGeom.size(), SizemodeMax); | ||||
1241 | if (adjSize != moveResizeGeom.size()) { | ||||
1242 | QRect r(moveResizeGeom); | ||||
1243 | moveResizeGeom.setSize(adjSize); | ||||
1244 | moveResizeGeom.moveCenter(r.center()); | ||||
1245 | } | ||||
1246 | setMoveResizeGeometry(moveResizeGeom); | ||||
1247 | } | ||||
1248 | } else { | ||||
1249 | // first move, then snap, then check bounds | ||||
1250 | QRect moveResizeGeom = moveResizeGeometry(); | ||||
1251 | moveResizeGeom.moveTopLeft(topleft); | ||||
1252 | moveResizeGeom.moveTopLeft(workspace()->adjustClientPosition(this, moveResizeGeom.topLeft(), | ||||
1253 | isUnrestrictedMoveResize())); | ||||
1254 | setMoveResizeGeometry(moveResizeGeom); | ||||
1255 | | ||||
1256 | if (!isUnrestrictedMoveResize()) { | ||||
1257 | const QRegion strut = workspace()->restrictedMoveArea(desktop()); // Strut areas | ||||
1258 | QRegion availableArea(workspace()->clientArea(FullArea, -1, 0)); // On the screen | ||||
1259 | availableArea -= strut; // Strut areas | ||||
1260 | bool transposed = false; | ||||
1261 | int requiredPixels; | ||||
1262 | QRect bTitleRect = titleBarRect(transposed, requiredPixels); | ||||
1263 | for (;;) { | ||||
1264 | QRect moveResizeGeom = moveResizeGeometry(); | ||||
1265 | const QRect titleRect(bTitleRect.translated(moveResizeGeom.topLeft())); | ||||
1266 | int visiblePixels = 0; | ||||
1267 | for (const QRect &rect : availableArea) { | ||||
1268 | const QRect r = rect & titleRect; | ||||
1269 | if ((transposed && r.width() == titleRect.width()) || // Only the full size regions... | ||||
1270 | (!transposed && r.height() == titleRect.height())) // ...prevents long slim areas | ||||
1271 | visiblePixels += r.width() * r.height(); | ||||
1272 | } | ||||
1273 | if (visiblePixels >= requiredPixels) | ||||
1274 | break; // We have reached a valid position | ||||
1275 | | ||||
1276 | // (esp.) if there're more screens with different struts (panels) it the titlebar | ||||
1277 | // will be movable outside the movearea (covering one of the panels) until it | ||||
1278 | // crosses the panel "too much" (not enough visiblePixels) and then stucks because | ||||
1279 | // it's usually only pushed by 1px to either direction | ||||
1280 | // so we first check whether we intersect suc strut and move the window below it | ||||
1281 | // immediately (it's still possible to hit the visiblePixels >= titlebarArea break | ||||
1282 | // by moving the window slightly downwards, but it won't stuck) | ||||
1283 | // see bug #274466 | ||||
1284 | // and bug #301805 for why we can't just match the titlearea against the screen | ||||
1285 | if (screens()->count() > 1) { // optimization | ||||
1286 | // TODO: could be useful on partial screen struts (half-width panels etc.) | ||||
1287 | int newTitleTop = -1; | ||||
1288 | for (const QRect &r : strut) { | ||||
1289 | if (r.top() == 0 && r.width() > r.height() && // "top panel" | ||||
1290 | r.intersects(moveResizeGeom) && moveResizeGeom.top() < r.bottom()) { | ||||
1291 | newTitleTop = r.bottom() + 1; | ||||
1292 | break; | ||||
1293 | } | ||||
1294 | } | ||||
1295 | if (newTitleTop > -1) { | ||||
1296 | moveResizeGeom.moveTop(newTitleTop); // invalid position, possibly on screen change | ||||
1297 | setMoveResizeGeometry(moveResizeGeom); | ||||
1298 | break; | ||||
1299 | } | ||||
1300 | } | ||||
1301 | | ||||
1302 | int dx = sign(previousMoveResizeGeom.x() - moveResizeGeom.x()), | ||||
1303 | dy = sign(previousMoveResizeGeom.y() - moveResizeGeom.y()); | ||||
1304 | if (visiblePixels && dx) // means there's no full width cap -> favor horizontally | ||||
1305 | dy = 0; | ||||
1306 | else if (dy) | ||||
1307 | dx = 0; | ||||
1308 | | ||||
1309 | // Move it back | ||||
1310 | moveResizeGeom.translate(dx, dy); | ||||
1311 | setMoveResizeGeometry(moveResizeGeom); | ||||
1312 | | ||||
1313 | if (moveResizeGeom == previousMoveResizeGeom) { | ||||
1314 | break; // Prevent lockup | ||||
1315 | } | ||||
1316 | } | ||||
1317 | } | ||||
1318 | } | ||||
1319 | if (moveResizeGeometry().topLeft() != previousMoveResizeGeom.topLeft()) | ||||
1320 | update = true; | ||||
1321 | } else | ||||
1322 | abort(); | ||||
1323 | | ||||
1324 | if (!update) | ||||
1325 | return; | ||||
1326 | | ||||
1327 | if (isResize() && !haveResizeEffect()) { | ||||
1328 | doResizeSync(); | ||||
1329 | } else | ||||
1330 | performMoveResize(); | ||||
1331 | | ||||
1332 | if (isMove()) { | ||||
1333 | ScreenEdges::self()->check(globalPos, QDateTime::fromMSecsSinceEpoch(xTime())); | ||||
1334 | } | ||||
1335 | } | ||||
1336 | | ||||
1337 | void AbstractClient::performMoveResize() | ||||
1338 | { | ||||
1339 | const QRect &moveResizeGeom = moveResizeGeometry(); | ||||
1340 | if (isMove() || (isResize() && !haveResizeEffect())) { | ||||
1341 | setFrameGeometry(moveResizeGeom); | ||||
1342 | } | ||||
1343 | doPerformMoveResize(); | ||||
1344 | if (isResize()) | ||||
1345 | addRepaintFull(); | ||||
1346 | positionGeometryTip(); | ||||
1347 | emit clientStepUserMovedResized(this, moveResizeGeom); | ||||
1348 | } | ||||
1349 | | ||||
760 | bool AbstractClient::hasStrut() const | 1350 | bool AbstractClient::hasStrut() const | ||
761 | { | 1351 | { | ||
762 | return false; | 1352 | return false; | ||
763 | } | 1353 | } | ||
764 | 1354 | | |||
765 | void AbstractClient::setupWindowManagementInterface() | 1355 | void AbstractClient::setupWindowManagementInterface() | ||
766 | { | 1356 | { | ||
767 | if (m_windowManagementInterface) { | 1357 | if (m_windowManagementInterface) { | ||
▲ Show 20 Lines • Show All 1274 Lines • ▼ Show 20 Line(s) | |||||
2042 | 2632 | | |||
2043 | QRect AbstractClient::clientRectToFrameRect(const QRect &rect) const | 2633 | QRect AbstractClient::clientRectToFrameRect(const QRect &rect) const | ||
2044 | { | 2634 | { | ||
2045 | const QPoint position = clientPosToFramePos(rect.topLeft()); | 2635 | const QPoint position = clientPosToFramePos(rect.topLeft()); | ||
2046 | const QSize size = clientSizeToFrameSize(rect.size()); | 2636 | const QSize size = clientSizeToFrameSize(rect.size()); | ||
2047 | return QRect(position, size); | 2637 | return QRect(position, size); | ||
2048 | } | 2638 | } | ||
2049 | 2639 | | |||
2640 | void AbstractClient::setElectricBorderMode(QuickTileMode mode) | ||||
2641 | { | ||||
2642 | if (mode != QuickTileMode(QuickTileFlag::Maximize)) { | ||||
2643 | // sanitize the mode, ie. simplify "invalid" combinations | ||||
2644 | if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Horizontal)) | ||||
2645 | mode &= ~QuickTileMode(QuickTileFlag::Horizontal); | ||||
2646 | if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Vertical)) | ||||
2647 | mode &= ~QuickTileMode(QuickTileFlag::Vertical); | ||||
2648 | } | ||||
2649 | m_electricMode = mode; | ||||
2650 | } | ||||
2651 | | ||||
2652 | void AbstractClient::setElectricBorderMaximizing(bool maximizing) | ||||
2653 | { | ||||
2654 | m_electricMaximizing = maximizing; | ||||
2655 | if (maximizing) | ||||
2656 | outline()->show(electricBorderMaximizeGeometry(Cursor::pos(), desktop()), moveResizeGeometry()); | ||||
2657 | else | ||||
2658 | outline()->hide(); | ||||
2659 | elevate(maximizing); | ||||
2660 | } | ||||
2661 | | ||||
2662 | QRect AbstractClient::electricBorderMaximizeGeometry(QPoint pos, int desktop) | ||||
2663 | { | ||||
2664 | if (electricBorderMode() == QuickTileMode(QuickTileFlag::Maximize)) { | ||||
2665 | if (maximizeMode() == MaximizeFull) | ||||
2666 | return geometryRestore(); | ||||
2667 | else | ||||
2668 | return workspace()->clientArea(MaximizeArea, pos, desktop); | ||||
2669 | } | ||||
2670 | | ||||
2671 | QRect ret = workspace()->clientArea(MaximizeArea, pos, desktop); | ||||
2672 | if (electricBorderMode() & QuickTileFlag::Left) | ||||
2673 | ret.setRight(ret.left()+ret.width()/2 - 1); | ||||
2674 | else if (electricBorderMode() & QuickTileFlag::Right) | ||||
2675 | ret.setLeft(ret.right()-(ret.width()-ret.width()/2) + 1); | ||||
2676 | if (electricBorderMode() & QuickTileFlag::Top) | ||||
2677 | ret.setBottom(ret.top()+ret.height()/2 - 1); | ||||
2678 | else if (electricBorderMode() & QuickTileFlag::Bottom) | ||||
2679 | ret.setTop(ret.bottom()-(ret.height()-ret.height()/2) + 1); | ||||
2680 | | ||||
2681 | return ret; | ||||
2682 | } | ||||
2683 | | ||||
2684 | void AbstractClient::setQuickTileMode(QuickTileMode mode, bool keyboard) | ||||
2685 | { | ||||
2686 | // Only allow quick tile on a regular window. | ||||
2687 | if (!isResizable()) { | ||||
2688 | return; | ||||
2689 | } | ||||
2690 | | ||||
2691 | workspace()->updateFocusMousePosition(Cursor::pos()); // may cause leave event | ||||
2692 | | ||||
2693 | GeometryUpdatesBlocker blocker(this); | ||||
2694 | | ||||
2695 | if (mode == QuickTileMode(QuickTileFlag::Maximize)) { | ||||
2696 | m_quickTileMode = int(QuickTileFlag::None); | ||||
2697 | if (maximizeMode() == MaximizeFull) { | ||||
2698 | setMaximize(false, false); | ||||
2699 | } else { | ||||
2700 | QRect prev_geom_restore = geometryRestore(); // setMaximize() would set moveResizeGeom as geom_restore | ||||
2701 | m_quickTileMode = int(QuickTileFlag::Maximize); | ||||
2702 | setMaximize(true, true); | ||||
2703 | QRect clientArea = workspace()->clientArea(MaximizeArea, this); | ||||
2704 | if (frameGeometry().top() != clientArea.top()) { | ||||
2705 | QRect r(frameGeometry()); | ||||
2706 | r.moveTop(clientArea.top()); | ||||
2707 | setFrameGeometry(r); | ||||
2708 | } | ||||
2709 | setGeometryRestore(prev_geom_restore); | ||||
2710 | } | ||||
2711 | emit quickTileModeChanged(); | ||||
2712 | return; | ||||
2713 | } | ||||
2714 | | ||||
2715 | // sanitize the mode, ie. simplify "invalid" combinations | ||||
2716 | if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Horizontal)) | ||||
2717 | mode &= ~QuickTileMode(QuickTileFlag::Horizontal); | ||||
2718 | if ((mode & QuickTileFlag::Vertical) == QuickTileMode(QuickTileFlag::Vertical)) | ||||
2719 | mode &= ~QuickTileMode(QuickTileFlag::Vertical); | ||||
2720 | | ||||
2721 | setElectricBorderMode(mode); // used by ::electricBorderMaximizeGeometry(.) | ||||
2722 | | ||||
2723 | // restore from maximized so that it is possible to tile maximized windows with one hit or by dragging | ||||
2724 | if (maximizeMode() != MaximizeRestore) { | ||||
2725 | | ||||
2726 | if (mode != QuickTileMode(QuickTileFlag::None)) { | ||||
2727 | // decorations may turn off some borders when tiled | ||||
2728 | const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet; | ||||
2729 | m_quickTileMode = int(QuickTileFlag::None); // Temporary, so the maximize code doesn't get all confused | ||||
2730 | | ||||
2731 | setMaximize(false, false); | ||||
2732 | | ||||
2733 | setFrameGeometry(electricBorderMaximizeGeometry(keyboard ? frameGeometry().center() : Cursor::pos(), desktop()), geom_mode); | ||||
2734 | // Store the mode change | ||||
2735 | m_quickTileMode = mode; | ||||
2736 | } else { | ||||
2737 | m_quickTileMode = mode; | ||||
2738 | setMaximize(false, false); | ||||
2739 | } | ||||
2740 | | ||||
2741 | emit quickTileModeChanged(); | ||||
2742 | | ||||
2743 | return; | ||||
2744 | } | ||||
2745 | | ||||
2746 | if (mode != QuickTileMode(QuickTileFlag::None)) { | ||||
2747 | QPoint whichScreen = keyboard ? frameGeometry().center() : Cursor::pos(); | ||||
2748 | | ||||
2749 | // If trying to tile to the side that the window is already tiled to move the window to the next | ||||
2750 | // screen if it exists, otherwise toggle the mode (set QuickTileFlag::None) | ||||
2751 | if (quickTileMode() == mode) { | ||||
2752 | const int numScreens = screens()->count(); | ||||
2753 | const int curScreen = screen(); | ||||
2754 | int nextScreen = curScreen; | ||||
2755 | QVarLengthArray<QRect> screens(numScreens); | ||||
2756 | for (int i = 0; i < numScreens; ++i) // Cache | ||||
2757 | screens[i] = Screens::self()->geometry(i); | ||||
2758 | for (int i = 0; i < numScreens; ++i) { | ||||
2759 | | ||||
2760 | if (i == curScreen) | ||||
2761 | continue; | ||||
2762 | | ||||
2763 | if (screens[i].bottom() <= screens[curScreen].top() || screens[i].top() >= screens[curScreen].bottom()) | ||||
2764 | continue; // not in horizontal line | ||||
2765 | | ||||
2766 | const int x = screens[i].center().x(); | ||||
2767 | if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Left)) { | ||||
2768 | if (x >= screens[curScreen].center().x() || (curScreen != nextScreen && x <= screens[nextScreen].center().x())) | ||||
2769 | continue; // not left of current or more left then found next | ||||
2770 | } else if ((mode & QuickTileFlag::Horizontal) == QuickTileMode(QuickTileFlag::Right)) { | ||||
2771 | if (x <= screens[curScreen].center().x() || (curScreen != nextScreen && x >= screens[nextScreen].center().x())) | ||||
2772 | continue; // not right of current or more right then found next | ||||
2773 | } | ||||
2774 | | ||||
2775 | nextScreen = i; | ||||
2776 | } | ||||
2777 | | ||||
2778 | if (nextScreen == curScreen) { | ||||
2779 | mode = QuickTileFlag::None; // No other screens, toggle tiling | ||||
2780 | } else { | ||||
2781 | // Move to other screen | ||||
2782 | setFrameGeometry(geometryRestore().translated(screens[nextScreen].topLeft() - screens[curScreen].topLeft())); | ||||
2783 | whichScreen = screens[nextScreen].center(); | ||||
2784 | | ||||
2785 | // Swap sides | ||||
2786 | if (mode & QuickTileFlag::Horizontal) { | ||||
2787 | mode = (~mode & QuickTileFlag::Horizontal) | (mode & QuickTileFlag::Vertical); | ||||
2788 | } | ||||
2789 | } | ||||
2790 | setElectricBorderMode(mode); // used by ::electricBorderMaximizeGeometry(.) | ||||
2791 | } else if (quickTileMode() == QuickTileMode(QuickTileFlag::None)) { | ||||
2792 | // Not coming out of an existing tile, not shifting monitors, we're setting a brand new tile. | ||||
2793 | // Store geometry first, so we can go out of this tile later. | ||||
2794 | setGeometryRestore(frameGeometry()); | ||||
2795 | } | ||||
2796 | | ||||
2797 | if (mode != QuickTileMode(QuickTileFlag::None)) { | ||||
2798 | m_quickTileMode = mode; | ||||
2799 | // decorations may turn off some borders when tiled | ||||
2800 | const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet; | ||||
2801 | // Temporary, so the maximize code doesn't get all confused | ||||
2802 | m_quickTileMode = int(QuickTileFlag::None); | ||||
2803 | setFrameGeometry(electricBorderMaximizeGeometry(whichScreen, desktop()), geom_mode); | ||||
2804 | } | ||||
2805 | | ||||
2806 | // Store the mode change | ||||
2807 | m_quickTileMode = mode; | ||||
2808 | } | ||||
2809 | | ||||
2810 | if (mode == QuickTileMode(QuickTileFlag::None)) { | ||||
2811 | m_quickTileMode = int(QuickTileFlag::None); | ||||
2812 | // Untiling, so just restore geometry, and we're done. | ||||
2813 | if (!geometryRestore().isValid()) // invalid if we started maximized and wait for placement | ||||
2814 | setGeometryRestore(frameGeometry()); | ||||
2815 | // decorations may turn off some borders when tiled | ||||
2816 | const ForceGeometry_t geom_mode = isDecorated() ? ForceGeometrySet : NormalGeometrySet; | ||||
2817 | setFrameGeometry(geometryRestore(), geom_mode); | ||||
2818 | checkWorkspacePosition(); // Just in case it's a different screen | ||||
2819 | } | ||||
2820 | emit quickTileModeChanged(); | ||||
2821 | } | ||||
2822 | | ||||
2823 | void AbstractClient::sendToScreen(int newScreen) | ||||
2824 | { | ||||
2825 | newScreen = rules()->checkScreen(newScreen); | ||||
2826 | if (isActive()) { | ||||
2827 | screens()->setCurrent(newScreen); | ||||
2828 | // might impact the layer of a fullscreen window | ||||
2829 | foreach (AbstractClient *cc, workspace()->allClientList()) { | ||||
2830 | if (cc->isFullScreen() && cc->screen() == newScreen) { | ||||
2831 | cc->updateLayer(); | ||||
2832 | } | ||||
2833 | } | ||||
2834 | } | ||||
2835 | if (screen() == newScreen) // Don't use isOnScreen(), that's true even when only partially | ||||
2836 | return; | ||||
2837 | | ||||
2838 | GeometryUpdatesBlocker blocker(this); | ||||
2839 | | ||||
2840 | // operating on the maximized / quicktiled window would leave the old geom_restore behind, | ||||
2841 | // so we clear the state first | ||||
2842 | MaximizeMode maxMode = maximizeMode(); | ||||
2843 | QuickTileMode qtMode = quickTileMode(); | ||||
2844 | if (maxMode != MaximizeRestore) | ||||
2845 | maximize(MaximizeRestore); | ||||
2846 | if (qtMode != QuickTileMode(QuickTileFlag::None)) | ||||
2847 | setQuickTileMode(QuickTileFlag::None, true); | ||||
2848 | | ||||
2849 | QRect oldScreenArea = workspace()->clientArea(MaximizeArea, this); | ||||
2850 | QRect screenArea = workspace()->clientArea(MaximizeArea, newScreen, desktop()); | ||||
2851 | | ||||
2852 | // the window can have its center so that the position correction moves the new center onto | ||||
2853 | // the old screen, what will tile it where it is. Ie. the screen is not changed | ||||
2854 | // this happens esp. with electric border quicktiling | ||||
2855 | if (qtMode != QuickTileMode(QuickTileFlag::None)) | ||||
2856 | keepInArea(oldScreenArea); | ||||
2857 | | ||||
2858 | QRect oldGeom = frameGeometry(); | ||||
2859 | QRect newGeom = oldGeom; | ||||
2860 | // move the window to have the same relative position to the center of the screen | ||||
2861 | // (i.e. one near the middle of the right edge will also end up near the middle of the right edge) | ||||
2862 | QPoint center = newGeom.center() - oldScreenArea.center(); | ||||
2863 | center.setX(center.x() * screenArea.width() / oldScreenArea.width()); | ||||
2864 | center.setY(center.y() * screenArea.height() / oldScreenArea.height()); | ||||
2865 | center += screenArea.center(); | ||||
2866 | newGeom.moveCenter(center); | ||||
2867 | setFrameGeometry(newGeom); | ||||
2868 | | ||||
2869 | // If the window was inside the old screen area, explicitly make sure its inside also the new screen area. | ||||
2870 | // Calling checkWorkspacePosition() should ensure that, but when moving to a small screen the window could | ||||
2871 | // be big enough to overlap outside of the new screen area, making struts from other screens come into effect, | ||||
2872 | // which could alter the resulting geometry. | ||||
2873 | if (oldScreenArea.contains(oldGeom)) { | ||||
2874 | keepInArea(screenArea); | ||||
2875 | } | ||||
2876 | | ||||
2877 | // align geom_restore - checkWorkspacePosition operates on it | ||||
2878 | setGeometryRestore(frameGeometry()); | ||||
2879 | | ||||
2880 | checkWorkspacePosition(oldGeom); | ||||
2881 | | ||||
2882 | // re-align geom_restore to constrained geometry | ||||
2883 | setGeometryRestore(frameGeometry()); | ||||
2884 | | ||||
2885 | // finally reset special states | ||||
2886 | // NOTICE that MaximizeRestore/QuickTileFlag::None checks are required. | ||||
2887 | // eg. setting QuickTileFlag::None would break maximization | ||||
2888 | if (maxMode != MaximizeRestore) | ||||
2889 | maximize(maxMode); | ||||
2890 | if (qtMode != QuickTileMode(QuickTileFlag::None) && qtMode != quickTileMode()) | ||||
2891 | setQuickTileMode(qtMode, true); | ||||
2892 | | ||||
2893 | auto tso = workspace()->ensureStackingOrder(transients()); | ||||
2894 | for (auto it = tso.constBegin(), end = tso.constEnd(); it != end; ++it) | ||||
2895 | (*it)->sendToScreen(newScreen); | ||||
2896 | } | ||||
2897 | | ||||
2898 | void AbstractClient::checkWorkspacePosition(QRect oldGeometry, int oldDesktop, QRect oldClientGeometry) | ||||
2899 | { | ||||
2900 | enum { Left = 0, Top, Right, Bottom }; | ||||
2901 | const int border[4] = { borderLeft(), borderTop(), borderRight(), borderBottom() }; | ||||
2902 | if( !oldGeometry.isValid()) | ||||
2903 | oldGeometry = frameGeometry(); | ||||
2904 | if( oldDesktop == -2 ) | ||||
2905 | oldDesktop = desktop(); | ||||
2906 | if (!oldClientGeometry.isValid()) | ||||
2907 | oldClientGeometry = oldGeometry.adjusted(border[Left], border[Top], -border[Right], -border[Bottom]); | ||||
2908 | if (isDesktop()) | ||||
2909 | return; | ||||
2910 | if (isFullScreen()) { | ||||
2911 | QRect area = workspace()->clientArea(FullScreenArea, this); | ||||
2912 | if (frameGeometry() != area) | ||||
2913 | setFrameGeometry(area); | ||||
2914 | return; | ||||
2915 | } | ||||
2916 | if (isDock()) | ||||
2917 | return; | ||||
2918 | | ||||
2919 | if (maximizeMode() != MaximizeRestore) { | ||||
2920 | // TODO update geom_restore? | ||||
2921 | changeMaximize(false, false, true); // adjust size | ||||
2922 | const QRect screenArea = workspace()->clientArea(ScreenArea, this); | ||||
2923 | QRect geom = frameGeometry(); | ||||
2924 | checkOffscreenPosition(&geom, screenArea); | ||||
2925 | setFrameGeometry(geom); | ||||
2926 | return; | ||||
2927 | } | ||||
2928 | | ||||
2929 | if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { | ||||
2930 | setFrameGeometry(electricBorderMaximizeGeometry(frameGeometry().center(), desktop())); | ||||
2931 | return; | ||||
2932 | } | ||||
2933 | | ||||
2934 | // this can be true only if this window was mapped before KWin | ||||
2935 | // was started - in such case, don't adjust position to workarea, | ||||
2936 | // because the window already had its position, and if a window | ||||
2937 | // with a strut altering the workarea would be managed in initialization | ||||
2938 | // after this one, this window would be moved | ||||
2939 | if (!workspace() || workspace()->initializing()) | ||||
2940 | return; | ||||
2941 | | ||||
2942 | // If the window was touching an edge before but not now move it so it is again. | ||||
2943 | // Old and new maximums have different starting values so windows on the screen | ||||
2944 | // edge will move when a new strut is placed on the edge. | ||||
2945 | QRect oldScreenArea; | ||||
2946 | if( workspace()->inUpdateClientArea()) { | ||||
2947 | // we need to find the screen area as it was before the change | ||||
2948 | oldScreenArea = QRect( 0, 0, workspace()->oldDisplayWidth(), workspace()->oldDisplayHeight()); | ||||
2949 | int distance = INT_MAX; | ||||
2950 | foreach(const QRect &r, workspace()->previousScreenSizes()) { | ||||
2951 | int d = r.contains( oldGeometry.center()) ? 0 : ( r.center() - oldGeometry.center()).manhattanLength(); | ||||
2952 | if( d < distance ) { | ||||
2953 | distance = d; | ||||
2954 | oldScreenArea = r; | ||||
2955 | } | ||||
2956 | } | ||||
2957 | } else { | ||||
2958 | oldScreenArea = workspace()->clientArea(ScreenArea, oldGeometry.center(), oldDesktop); | ||||
2959 | } | ||||
2960 | const QRect oldGeomTall = QRect(oldGeometry.x(), oldScreenArea.y(), oldGeometry.width(), oldScreenArea.height()); // Full screen height | ||||
2961 | const QRect oldGeomWide = QRect(oldScreenArea.x(), oldGeometry.y(), oldScreenArea.width(), oldGeometry.height()); // Full screen width | ||||
2962 | int oldTopMax = oldScreenArea.y(); | ||||
2963 | int oldRightMax = oldScreenArea.x() + oldScreenArea.width(); | ||||
2964 | int oldBottomMax = oldScreenArea.y() + oldScreenArea.height(); | ||||
2965 | int oldLeftMax = oldScreenArea.x(); | ||||
2966 | const QRect screenArea = workspace()->clientArea(ScreenArea, geometryRestore().center(), desktop()); | ||||
2967 | int topMax = screenArea.y(); | ||||
2968 | int rightMax = screenArea.x() + screenArea.width(); | ||||
2969 | int bottomMax = screenArea.y() + screenArea.height(); | ||||
2970 | int leftMax = screenArea.x(); | ||||
2971 | QRect newGeom = geometryRestore(); // geometry(); | ||||
2972 | QRect newClientGeom = newGeom.adjusted(border[Left], border[Top], -border[Right], -border[Bottom]); | ||||
2973 | const QRect newGeomTall = QRect(newGeom.x(), screenArea.y(), newGeom.width(), screenArea.height()); // Full screen height | ||||
2974 | const QRect newGeomWide = QRect(screenArea.x(), newGeom.y(), screenArea.width(), newGeom.height()); // Full screen width | ||||
2975 | // Get the max strut point for each side where the window is (E.g. Highest point for | ||||
2976 | // the bottom struts bounded by the window's left and right sides). | ||||
2977 | | ||||
2978 | // These 4 compute old bounds ... | ||||
2979 | auto moveAreaFunc = workspace()->inUpdateClientArea() ? | ||||
2980 | &Workspace::previousRestrictedMoveArea : //... the restricted areas changed | ||||
2981 | &Workspace::restrictedMoveArea; //... when e.g. active desktop or screen changes | ||||
2982 | | ||||
2983 | for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaTop)) { | ||||
2984 | QRect rect = r & oldGeomTall; | ||||
2985 | if (!rect.isEmpty()) | ||||
2986 | oldTopMax = qMax(oldTopMax, rect.y() + rect.height()); | ||||
2987 | } | ||||
2988 | for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaRight)) { | ||||
2989 | QRect rect = r & oldGeomWide; | ||||
2990 | if (!rect.isEmpty()) | ||||
2991 | oldRightMax = qMin(oldRightMax, rect.x()); | ||||
2992 | } | ||||
2993 | for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaBottom)) { | ||||
2994 | QRect rect = r & oldGeomTall; | ||||
2995 | if (!rect.isEmpty()) | ||||
2996 | oldBottomMax = qMin(oldBottomMax, rect.y()); | ||||
2997 | } | ||||
2998 | for (const QRect &r : (workspace()->*moveAreaFunc)(oldDesktop, StrutAreaLeft)) { | ||||
2999 | QRect rect = r & oldGeomWide; | ||||
3000 | if (!rect.isEmpty()) | ||||
3001 | oldLeftMax = qMax(oldLeftMax, rect.x() + rect.width()); | ||||
3002 | } | ||||
3003 | | ||||
3004 | // These 4 compute new bounds | ||||
3005 | for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaTop)) { | ||||
3006 | QRect rect = r & newGeomTall; | ||||
3007 | if (!rect.isEmpty()) | ||||
3008 | topMax = qMax(topMax, rect.y() + rect.height()); | ||||
3009 | } | ||||
3010 | for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaRight)) { | ||||
3011 | QRect rect = r & newGeomWide; | ||||
3012 | if (!rect.isEmpty()) | ||||
3013 | rightMax = qMin(rightMax, rect.x()); | ||||
3014 | } | ||||
3015 | for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaBottom)) { | ||||
3016 | QRect rect = r & newGeomTall; | ||||
3017 | if (!rect.isEmpty()) | ||||
3018 | bottomMax = qMin(bottomMax, rect.y()); | ||||
3019 | } | ||||
3020 | for (const QRect &r : workspace()->restrictedMoveArea(desktop(), StrutAreaLeft)) { | ||||
3021 | QRect rect = r & newGeomWide; | ||||
3022 | if (!rect.isEmpty()) | ||||
3023 | leftMax = qMax(leftMax, rect.x() + rect.width()); | ||||
3024 | } | ||||
3025 | | ||||
3026 | | ||||
3027 | // Check if the sides were inside or touching but are no longer | ||||
3028 | bool keep[4] = {false, false, false, false}; | ||||
3029 | bool save[4] = {false, false, false, false}; | ||||
3030 | int padding[4] = {0, 0, 0, 0}; | ||||
3031 | if (oldGeometry.x() >= oldLeftMax) | ||||
3032 | save[Left] = newGeom.x() < leftMax; | ||||
3033 | if (oldGeometry.x() == oldLeftMax) | ||||
3034 | keep[Left] = newGeom.x() != leftMax; | ||||
3035 | else if (oldClientGeometry.x() == oldLeftMax && newClientGeom.x() != leftMax) { | ||||
3036 | padding[0] = border[Left]; | ||||
3037 | keep[Left] = true; | ||||
3038 | } | ||||
3039 | if (oldGeometry.y() >= oldTopMax) | ||||
3040 | save[Top] = newGeom.y() < topMax; | ||||
3041 | if (oldGeometry.y() == oldTopMax) | ||||
3042 | keep[Top] = newGeom.y() != topMax; | ||||
3043 | else if (oldClientGeometry.y() == oldTopMax && newClientGeom.y() != topMax) { | ||||
3044 | padding[1] = border[Left]; | ||||
3045 | keep[Top] = true; | ||||
3046 | } | ||||
3047 | if (oldGeometry.right() <= oldRightMax - 1) | ||||
3048 | save[Right] = newGeom.right() > rightMax - 1; | ||||
3049 | if (oldGeometry.right() == oldRightMax - 1) | ||||
3050 | keep[Right] = newGeom.right() != rightMax - 1; | ||||
3051 | else if (oldClientGeometry.right() == oldRightMax - 1 && newClientGeom.right() != rightMax - 1) { | ||||
3052 | padding[2] = border[Right]; | ||||
3053 | keep[Right] = true; | ||||
3054 | } | ||||
3055 | if (oldGeometry.bottom() <= oldBottomMax - 1) | ||||
3056 | save[Bottom] = newGeom.bottom() > bottomMax - 1; | ||||
3057 | if (oldGeometry.bottom() == oldBottomMax - 1) | ||||
3058 | keep[Bottom] = newGeom.bottom() != bottomMax - 1; | ||||
3059 | else if (oldClientGeometry.bottom() == oldBottomMax - 1 && newClientGeom.bottom() != bottomMax - 1) { | ||||
3060 | padding[3] = border[Bottom]; | ||||
3061 | keep[Bottom] = true; | ||||
3062 | } | ||||
3063 | | ||||
3064 | // if randomly touches opposing edges, do not favor either | ||||
3065 | if (keep[Left] && keep[Right]) { | ||||
3066 | keep[Left] = keep[Right] = false; | ||||
3067 | padding[0] = padding[2] = 0; | ||||
3068 | } | ||||
3069 | if (keep[Top] && keep[Bottom]) { | ||||
3070 | keep[Top] = keep[Bottom] = false; | ||||
3071 | padding[1] = padding[3] = 0; | ||||
3072 | } | ||||
3073 | | ||||
3074 | if (save[Left] || keep[Left]) | ||||
3075 | newGeom.moveLeft(qMax(leftMax, screenArea.x()) - padding[0]); | ||||
3076 | if (padding[0] && screens()->intersecting(newGeom) > 1) | ||||
3077 | newGeom.moveLeft(newGeom.left() + padding[0]); | ||||
3078 | if (save[Top] || keep[Top]) | ||||
3079 | newGeom.moveTop(qMax(topMax, screenArea.y()) - padding[1]); | ||||
3080 | if (padding[1] && screens()->intersecting(newGeom) > 1) | ||||
3081 | newGeom.moveTop(newGeom.top() + padding[1]); | ||||
3082 | if (save[Right] || keep[Right]) | ||||
3083 | newGeom.moveRight(qMin(rightMax - 1, screenArea.right()) + padding[2]); | ||||
3084 | if (padding[2] && screens()->intersecting(newGeom) > 1) | ||||
3085 | newGeom.moveRight(newGeom.right() - padding[2]); | ||||
3086 | if (oldGeometry.x() >= oldLeftMax && newGeom.x() < leftMax) | ||||
3087 | newGeom.setLeft(qMax(leftMax, screenArea.x())); | ||||
3088 | else if (oldClientGeometry.x() >= oldLeftMax && newGeom.x() + border[Left] < leftMax) { | ||||
3089 | newGeom.setLeft(qMax(leftMax, screenArea.x()) - border[Left]); | ||||
3090 | if (screens()->intersecting(newGeom) > 1) | ||||
3091 | newGeom.setLeft(newGeom.left() + border[Left]); | ||||
3092 | } | ||||
3093 | if (save[Bottom] || keep[Bottom]) | ||||
3094 | newGeom.moveBottom(qMin(bottomMax - 1, screenArea.bottom()) + padding[3]); | ||||
3095 | if (padding[3] && screens()->intersecting(newGeom) > 1) | ||||
3096 | newGeom.moveBottom(newGeom.bottom() - padding[3]); | ||||
3097 | if (oldGeometry.y() >= oldTopMax && newGeom.y() < topMax) | ||||
3098 | newGeom.setTop(qMax(topMax, screenArea.y())); | ||||
3099 | else if (oldClientGeometry.y() >= oldTopMax && newGeom.y() + border[Top] < topMax) { | ||||
3100 | newGeom.setTop(qMax(topMax, screenArea.y()) - border[Top]); | ||||
3101 | if (screens()->intersecting(newGeom) > 1) | ||||
3102 | newGeom.setTop(newGeom.top() + border[Top]); | ||||
3103 | } | ||||
3104 | | ||||
3105 | checkOffscreenPosition(&newGeom, screenArea); | ||||
3106 | // Obey size hints. TODO: We really should make sure it stays in the right place | ||||
3107 | if (!isShade()) | ||||
3108 | newGeom.setSize(adjustedSize(newGeom.size())); | ||||
3109 | | ||||
3110 | if (newGeom != frameGeometry()) | ||||
3111 | setFrameGeometry(newGeom); | ||||
3112 | } | ||||
3113 | | ||||
3114 | void AbstractClient::checkOffscreenPosition(QRect* geom, const QRect& screenArea) | ||||
3115 | { | ||||
3116 | if (geom->left() > screenArea.right()) { | ||||
3117 | geom->moveLeft(screenArea.right() - screenArea.width()/4); | ||||
3118 | } else if (geom->right() < screenArea.left()) { | ||||
3119 | geom->moveRight(screenArea.left() + screenArea.width()/4); | ||||
3120 | } | ||||
3121 | if (geom->top() > screenArea.bottom()) { | ||||
3122 | geom->moveTop(screenArea.bottom() - screenArea.height()/4); | ||||
3123 | } else if (geom->bottom() < screenArea.top()) { | ||||
3124 | geom->moveBottom(screenArea.top() + screenArea.width()/4); | ||||
3125 | } | ||||
3126 | } | ||||
3127 | | ||||
3128 | QSize AbstractClient::adjustedSize(const QSize& frame, Sizemode mode) const | ||||
3129 | { | ||||
3130 | // first, get the window size for the given frame size s | ||||
3131 | QSize wsize = frameSizeToClientSize(frame); | ||||
3132 | if (wsize.isEmpty()) | ||||
3133 | wsize = QSize(qMax(wsize.width(), 1), qMax(wsize.height(), 1)); | ||||
3134 | | ||||
3135 | return sizeForClientSize(wsize, mode, false); | ||||
3136 | } | ||||
3137 | | ||||
3138 | // this helper returns proper size even if the window is shaded | ||||
3139 | // see also the comment in X11Client::setGeometry() | ||||
3140 | QSize AbstractClient::adjustedSize() const | ||||
3141 | { | ||||
3142 | return sizeForClientSize(clientSize()); | ||||
3143 | } | ||||
3144 | | ||||
2050 | } | 3145 | } |