Changeset View
Changeset View
Standalone View
Standalone View
x11client.cpp
Show First 20 Lines • Show All 2937 Lines • ▼ Show 20 Line(s) | 2911 | { | |||
---|---|---|---|---|---|
2938 | screens()->setCurrent(this); | 2938 | screens()->setCurrent(this); | ||
2939 | workspace()->updateStackingOrder(); | 2939 | workspace()->updateStackingOrder(); | ||
2940 | // client itself is not damaged | 2940 | // client itself is not damaged | ||
2941 | addRepaintDuringGeometryUpdates(); | 2941 | addRepaintDuringGeometryUpdates(); | ||
2942 | updateGeometryBeforeUpdateBlocking(); | 2942 | updateGeometryBeforeUpdateBlocking(); | ||
2943 | emit geometryChanged(); | 2943 | emit geometryChanged(); | ||
2944 | } | 2944 | } | ||
2945 | 2945 | | |||
2946 | bool X11Client::belongToSameApplication(const X11Client *c1, const X11Client *c2, SameApplicationChecks checks) | ||||
2947 | { | ||||
2948 | bool same_app = false; | ||||
2949 | | ||||
2950 | // tests that definitely mean they belong together | ||||
2951 | if (c1 == c2) | ||||
2952 | same_app = true; | ||||
2953 | else if (c1->isTransient() && c2->hasTransient(c1, true)) | ||||
2954 | same_app = true; // c1 has c2 as mainwindow | ||||
2955 | else if (c2->isTransient() && c1->hasTransient(c2, true)) | ||||
2956 | same_app = true; // c2 has c1 as mainwindow | ||||
2957 | else if (c1->group() == c2->group()) | ||||
2958 | same_app = true; // same group | ||||
2959 | else if (c1->wmClientLeader() == c2->wmClientLeader() | ||||
2960 | && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(), | ||||
2961 | && c2->wmClientLeader() != c2->window()) // don't use in this test then | ||||
2962 | same_app = true; // same client leader | ||||
2963 | | ||||
2964 | // tests that mean they most probably don't belong together | ||||
2965 | else if ((c1->pid() != c2->pid() && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) | ||||
2966 | || c1->wmClientMachine(false) != c2->wmClientMachine(false)) | ||||
2967 | ; // different processes | ||||
2968 | else if (c1->wmClientLeader() != c2->wmClientLeader() | ||||
2969 | && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(), | ||||
2970 | && c2->wmClientLeader() != c2->window() // don't use in this test then | ||||
2971 | && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) | ||||
2972 | ; // different client leader | ||||
2973 | else if (!resourceMatch(c1, c2)) | ||||
2974 | ; // different apps | ||||
2975 | else if (!sameAppWindowRoleMatch(c1, c2, checks.testFlag(SameApplicationCheck::RelaxedForActive)) | ||||
2976 | && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) | ||||
2977 | ; // "different" apps | ||||
2978 | else if (c1->pid() == 0 || c2->pid() == 0) | ||||
2979 | ; // old apps that don't have _NET_WM_PID, consider them different | ||||
2980 | // if they weren't found to match above | ||||
2981 | else | ||||
2982 | same_app = true; // looks like it's the same app | ||||
2983 | | ||||
2984 | return same_app; | ||||
2985 | } | ||||
2986 | | ||||
2987 | // Non-transient windows with window role containing '#' are always | ||||
2988 | // considered belonging to different applications (unless | ||||
2989 | // the window role is exactly the same). KMainWindow sets | ||||
2990 | // window role this way by default, and different KMainWindow | ||||
2991 | // usually "are" different application from user's point of view. | ||||
2992 | // This help with no-focus-stealing for e.g. konqy reusing. | ||||
2993 | // On the other hand, if one of the windows is active, they are | ||||
2994 | // considered belonging to the same application. This is for | ||||
2995 | // the cases when opening new mainwindow directly from the application, | ||||
2996 | // e.g. 'Open New Window' in konqy ( active_hack == true ). | ||||
2997 | bool X11Client::sameAppWindowRoleMatch(const X11Client *c1, const X11Client *c2, bool active_hack) | ||||
2998 | { | ||||
2999 | if (c1->isTransient()) { | ||||
3000 | while (const X11Client *t = dynamic_cast<const X11Client *>(c1->transientFor())) | ||||
3001 | c1 = t; | ||||
3002 | if (c1->groupTransient()) | ||||
3003 | return c1->group() == c2->group(); | ||||
3004 | #if 0 | ||||
3005 | // if a group transient is in its own group, it didn't possibly have a group, | ||||
3006 | // and therefore should be considered belonging to the same app like | ||||
3007 | // all other windows from the same app | ||||
3008 | || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2; | ||||
3009 | #endif | ||||
3010 | } | ||||
3011 | if (c2->isTransient()) { | ||||
3012 | while (const X11Client *t = dynamic_cast<const X11Client *>(c2->transientFor())) | ||||
3013 | c2 = t; | ||||
3014 | if (c2->groupTransient()) | ||||
3015 | return c1->group() == c2->group(); | ||||
3016 | #if 0 | ||||
3017 | || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2; | ||||
3018 | #endif | ||||
3019 | } | ||||
3020 | int pos1 = c1->windowRole().indexOf('#'); | ||||
3021 | int pos2 = c2->windowRole().indexOf('#'); | ||||
3022 | if ((pos1 >= 0 && pos2 >= 0)) { | ||||
3023 | if (!active_hack) // without the active hack for focus stealing prevention, | ||||
3024 | return c1 == c2; // different mainwindows are always different apps | ||||
3025 | if (!c1->isActive() && !c2->isActive()) | ||||
3026 | return c1 == c2; | ||||
3027 | else | ||||
3028 | return true; | ||||
3029 | } | ||||
3030 | return true; | ||||
3031 | } | ||||
3032 | | ||||
3033 | /* | ||||
3034 | | ||||
3035 | Transiency stuff: ICCCM 4.1.2.6, NETWM 7.3 | ||||
3036 | | ||||
3037 | WM_TRANSIENT_FOR is basically means "this is my mainwindow". | ||||
3038 | For NET::Unknown windows, transient windows are considered to be NET::Dialog | ||||
3039 | windows, for compatibility with non-NETWM clients. KWin may adjust the value | ||||
3040 | of this property in some cases (window pointing to itself or creating a loop, | ||||
3041 | keeping NET::Splash windows above other windows from the same app, etc.). | ||||
3042 | | ||||
3043 | X11Client::transient_for_id is the value of the WM_TRANSIENT_FOR property, after | ||||
3044 | possibly being adjusted by KWin. X11Client::transient_for points to the Client | ||||
3045 | this Client is transient for, or is NULL. If X11Client::transient_for_id is | ||||
3046 | poiting to the root window, the window is considered to be transient | ||||
3047 | for the whole window group, as suggested in NETWM 7.3. | ||||
3048 | | ||||
3049 | In the case of group transient window, X11Client::transient_for is NULL, | ||||
3050 | and X11Client::groupTransient() returns true. Such window is treated as | ||||
3051 | if it were transient for every window in its window group that has been | ||||
3052 | mapped _before_ it (or, to be exact, was added to the same group before it). | ||||
3053 | Otherwise two group transients can create loops, which can lead very very | ||||
3054 | nasty things (bug #67914 and all its dupes). | ||||
3055 | | ||||
3056 | X11Client::original_transient_for_id is the value of the property, which | ||||
3057 | may be different if X11Client::transient_for_id if e.g. forcing NET::Splash | ||||
3058 | to be kept on top of its window group, or when the mainwindow is not mapped | ||||
3059 | yet, in which case the window is temporarily made group transient, | ||||
3060 | and when the mainwindow is mapped, transiency is re-evaluated. | ||||
3061 | | ||||
3062 | This can get a bit complicated with with e.g. two Konqueror windows created | ||||
3063 | by the same process. They should ideally appear like two independent applications | ||||
3064 | to the user. This should be accomplished by all windows in the same process | ||||
3065 | having the same window group (needs to be changed in Qt at the moment), and | ||||
3066 | using non-group transients poiting to their relevant mainwindow for toolwindows | ||||
3067 | etc. KWin should handle both group and non-group transient dialogs well. | ||||
3068 | | ||||
3069 | In other words: | ||||
3070 | - non-transient windows : isTransient() == false | ||||
3071 | - normal transients : transientFor() != NULL | ||||
3072 | - group transients : groupTransient() == true | ||||
3073 | | ||||
3074 | - list of mainwindows : mainClients() (call once and loop over the result) | ||||
3075 | - list of transients : transients() | ||||
3076 | - every window in the group : group()->members() | ||||
3077 | */ | ||||
3078 | | ||||
3079 | Xcb::TransientFor X11Client::fetchTransient() const | ||||
3080 | { | ||||
3081 | return Xcb::TransientFor(window()); | ||||
3082 | } | ||||
3083 | | ||||
3084 | void X11Client::readTransientProperty(Xcb::TransientFor &transientFor) | ||||
3085 | { | ||||
3086 | xcb_window_t new_transient_for_id = XCB_WINDOW_NONE; | ||||
3087 | if (transientFor.getTransientFor(&new_transient_for_id)) { | ||||
3088 | m_originalTransientForId = new_transient_for_id; | ||||
3089 | new_transient_for_id = verifyTransientFor(new_transient_for_id, true); | ||||
3090 | } else { | ||||
3091 | m_originalTransientForId = XCB_WINDOW_NONE; | ||||
3092 | new_transient_for_id = verifyTransientFor(XCB_WINDOW_NONE, false); | ||||
3093 | } | ||||
3094 | setTransient(new_transient_for_id); | ||||
3095 | } | ||||
3096 | | ||||
3097 | void X11Client::readTransient() | ||||
3098 | { | ||||
3099 | Xcb::TransientFor transientFor = fetchTransient(); | ||||
3100 | readTransientProperty(transientFor); | ||||
3101 | } | ||||
3102 | | ||||
3103 | void X11Client::setTransient(xcb_window_t new_transient_for_id) | ||||
3104 | { | ||||
3105 | if (new_transient_for_id != m_transientForId) { | ||||
3106 | removeFromMainClients(); | ||||
3107 | X11Client *transient_for = nullptr; | ||||
3108 | m_transientForId = new_transient_for_id; | ||||
3109 | if (m_transientForId != XCB_WINDOW_NONE && !groupTransient()) { | ||||
3110 | transient_for = workspace()->findClient(Predicate::WindowMatch, m_transientForId); | ||||
3111 | Q_ASSERT(transient_for != nullptr); // verifyTransient() had to check this | ||||
3112 | transient_for->addTransient(this); | ||||
3113 | } // checkGroup() will check 'check_active_modal' | ||||
3114 | setTransientFor(transient_for); | ||||
3115 | checkGroup(nullptr, true); // force, because transiency has changed | ||||
3116 | workspace()->updateClientLayer(this); | ||||
3117 | workspace()->resetUpdateToolWindowsTimer(); | ||||
3118 | emit transientChanged(); | ||||
3119 | } | ||||
3120 | } | ||||
3121 | | ||||
3122 | void X11Client::removeFromMainClients() | ||||
3123 | { | ||||
3124 | if (transientFor()) | ||||
3125 | transientFor()->removeTransient(this); | ||||
3126 | if (groupTransient()) { | ||||
3127 | for (auto it = group()->members().constBegin(); | ||||
3128 | it != group()->members().constEnd(); | ||||
3129 | ++it) | ||||
3130 | (*it)->removeTransient(this); | ||||
3131 | } | ||||
3132 | } | ||||
3133 | | ||||
3134 | // *sigh* this transiency handling is madness :( | ||||
3135 | // This one is called when destroying/releasing a window. | ||||
3136 | // It makes sure this client is removed from all grouping | ||||
3137 | // related lists. | ||||
3138 | void X11Client::cleanGrouping() | ||||
3139 | { | ||||
3140 | // qDebug() << "CLEANGROUPING:" << this; | ||||
3141 | // for ( auto it = group()->members().begin(); | ||||
3142 | // it != group()->members().end(); | ||||
3143 | // ++it ) | ||||
3144 | // qDebug() << "CL:" << *it; | ||||
3145 | // QList<X11Client *> mains; | ||||
3146 | // mains = mainClients(); | ||||
3147 | // for ( auto it = mains.begin(); | ||||
3148 | // it != mains.end(); | ||||
3149 | // ++it ) | ||||
3150 | // qDebug() << "MN:" << *it; | ||||
3151 | removeFromMainClients(); | ||||
3152 | // qDebug() << "CLEANGROUPING2:" << this; | ||||
3153 | // for ( auto it = group()->members().begin(); | ||||
3154 | // it != group()->members().end(); | ||||
3155 | // ++it ) | ||||
3156 | // qDebug() << "CL2:" << *it; | ||||
3157 | // mains = mainClients(); | ||||
3158 | // for ( auto it = mains.begin(); | ||||
3159 | // it != mains.end(); | ||||
3160 | // ++it ) | ||||
3161 | // qDebug() << "MN2:" << *it; | ||||
3162 | for (auto it = transients().constBegin(); | ||||
3163 | it != transients().constEnd(); | ||||
3164 | ) { | ||||
3165 | if ((*it)->transientFor() == this) { | ||||
3166 | removeTransient(*it); | ||||
3167 | it = transients().constBegin(); // restart, just in case something more has changed with the list | ||||
3168 | } else | ||||
3169 | ++it; | ||||
3170 | } | ||||
3171 | // qDebug() << "CLEANGROUPING3:" << this; | ||||
3172 | // for ( auto it = group()->members().begin(); | ||||
3173 | // it != group()->members().end(); | ||||
3174 | // ++it ) | ||||
3175 | // qDebug() << "CL3:" << *it; | ||||
3176 | // mains = mainClients(); | ||||
3177 | // for ( auto it = mains.begin(); | ||||
3178 | // it != mains.end(); | ||||
3179 | // ++it ) | ||||
3180 | // qDebug() << "MN3:" << *it; | ||||
3181 | // HACK | ||||
3182 | // removeFromMainClients() did remove 'this' from transient | ||||
3183 | // lists of all group members, but then made windows that | ||||
3184 | // were transient for 'this' group transient, which again | ||||
3185 | // added 'this' to those transient lists :( | ||||
3186 | QList<X11Client *> group_members = group()->members(); | ||||
3187 | group()->removeMember(this); | ||||
3188 | in_group = nullptr; | ||||
3189 | for (auto it = group_members.constBegin(); | ||||
3190 | it != group_members.constEnd(); | ||||
3191 | ++it) | ||||
3192 | (*it)->removeTransient(this); | ||||
3193 | // qDebug() << "CLEANGROUPING4:" << this; | ||||
3194 | // for ( auto it = group_members.begin(); | ||||
3195 | // it != group_members.end(); | ||||
3196 | // ++it ) | ||||
3197 | // qDebug() << "CL4:" << *it; | ||||
3198 | m_transientForId = XCB_WINDOW_NONE; | ||||
3199 | } | ||||
3200 | | ||||
3201 | // Make sure that no group transient is considered transient | ||||
3202 | // for a window that is (directly or indirectly) transient for it | ||||
3203 | // (including another group transients). | ||||
3204 | // Non-group transients not causing loops are checked in verifyTransientFor(). | ||||
3205 | void X11Client::checkGroupTransients() | ||||
3206 | { | ||||
3207 | for (auto it1 = group()->members().constBegin(); | ||||
3208 | it1 != group()->members().constEnd(); | ||||
3209 | ++it1) { | ||||
3210 | if (!(*it1)->groupTransient()) // check all group transients in the group | ||||
3211 | continue; // TODO optimize to check only the changed ones? | ||||
3212 | for (auto it2 = group()->members().constBegin(); | ||||
3213 | it2 != group()->members().constEnd(); | ||||
3214 | ++it2) { // group transients can be transient only for others in the group, | ||||
3215 | // so don't make them transient for the ones that are transient for it | ||||
3216 | if (*it1 == *it2) | ||||
3217 | continue; | ||||
3218 | for (AbstractClient* cl = (*it2)->transientFor(); | ||||
3219 | cl != nullptr; | ||||
3220 | cl = cl->transientFor()) { | ||||
3221 | if (cl == *it1) { | ||||
3222 | // don't use removeTransient(), that would modify *it2 too | ||||
3223 | (*it2)->removeTransientFromList(*it1); | ||||
3224 | continue; | ||||
3225 | } | ||||
3226 | } | ||||
3227 | // if *it1 and *it2 are both group transients, and are transient for each other, | ||||
3228 | // make only *it2 transient for *it1 (i.e. subwindow), as *it2 came later, | ||||
3229 | // and should be therefore on top of *it1 | ||||
3230 | // TODO This could possibly be optimized, it also requires hasTransient() to check for loops. | ||||
3231 | if ((*it2)->groupTransient() && (*it1)->hasTransient(*it2, true) && (*it2)->hasTransient(*it1, true)) | ||||
3232 | (*it2)->removeTransientFromList(*it1); | ||||
3233 | // if there are already windows W1 and W2, W2 being transient for W1, and group transient W3 | ||||
3234 | // is added, make it transient only for W2, not for W1, because it's already indirectly | ||||
3235 | // transient for it - the indirect transiency actually shouldn't break anything, | ||||
3236 | // but it can lead to exponentially expensive operations (#95231) | ||||
3237 | // TODO this is pretty slow as well | ||||
3238 | for (auto it3 = group()->members().constBegin(); | ||||
3239 | it3 != group()->members().constEnd(); | ||||
3240 | ++it3) { | ||||
3241 | if (*it1 == *it2 || *it2 == *it3 || *it1 == *it3) | ||||
3242 | continue; | ||||
3243 | if ((*it2)->hasTransient(*it1, false) && (*it3)->hasTransient(*it1, false)) { | ||||
3244 | if ((*it2)->hasTransient(*it3, true)) | ||||
3245 | (*it2)->removeTransientFromList(*it1); | ||||
3246 | if ((*it3)->hasTransient(*it2, true)) | ||||
3247 | (*it3)->removeTransientFromList(*it1); | ||||
3248 | } | ||||
3249 | } | ||||
3250 | } | ||||
3251 | } | ||||
3252 | } | ||||
3253 | | ||||
3254 | /** | ||||
3255 | * Check that the window is not transient for itself, and similar nonsense. | ||||
3256 | */ | ||||
3257 | xcb_window_t X11Client::verifyTransientFor(xcb_window_t new_transient_for, bool set) | ||||
3258 | { | ||||
3259 | xcb_window_t new_property_value = new_transient_for; | ||||
3260 | // make sure splashscreens are shown above all their app's windows, even though | ||||
3261 | // they're in Normal layer | ||||
3262 | if (isSplash() && new_transient_for == XCB_WINDOW_NONE) | ||||
3263 | new_transient_for = rootWindow(); | ||||
3264 | if (new_transient_for == XCB_WINDOW_NONE) { | ||||
3265 | if (set) // sometimes WM_TRANSIENT_FOR is set to None, instead of root window | ||||
3266 | new_property_value = new_transient_for = rootWindow(); | ||||
3267 | else | ||||
3268 | return XCB_WINDOW_NONE; | ||||
3269 | } | ||||
3270 | if (new_transient_for == window()) { // pointing to self | ||||
3271 | // also fix the property itself | ||||
3272 | qCWarning(KWIN_CORE) << "Client " << this << " has WM_TRANSIENT_FOR poiting to itself." ; | ||||
3273 | new_property_value = new_transient_for = rootWindow(); | ||||
3274 | } | ||||
3275 | // The transient_for window may be embedded in another application, | ||||
3276 | // so kwin cannot see it. Try to find the managed client for the | ||||
3277 | // window and fix the transient_for property if possible. | ||||
3278 | xcb_window_t before_search = new_transient_for; | ||||
3279 | while (new_transient_for != XCB_WINDOW_NONE | ||||
3280 | && new_transient_for != rootWindow() | ||||
3281 | && !workspace()->findClient(Predicate::WindowMatch, new_transient_for)) { | ||||
3282 | Xcb::Tree tree(new_transient_for); | ||||
3283 | if (tree.isNull()) { | ||||
3284 | break; | ||||
3285 | } | ||||
3286 | new_transient_for = tree->parent; | ||||
3287 | } | ||||
3288 | if (X11Client *new_transient_for_client = workspace()->findClient(Predicate::WindowMatch, new_transient_for)) { | ||||
3289 | if (new_transient_for != before_search) { | ||||
3290 | qCDebug(KWIN_CORE) << "Client " << this << " has WM_TRANSIENT_FOR poiting to non-toplevel window " | ||||
3291 | << before_search << ", child of " << new_transient_for_client << ", adjusting."; | ||||
3292 | new_property_value = new_transient_for; // also fix the property | ||||
3293 | } | ||||
3294 | } else | ||||
3295 | new_transient_for = before_search; // nice try | ||||
3296 | // loop detection | ||||
3297 | // group transients cannot cause loops, because they're considered transient only for non-transient | ||||
3298 | // windows in the group | ||||
3299 | int count = 20; | ||||
3300 | xcb_window_t loop_pos = new_transient_for; | ||||
3301 | while (loop_pos != XCB_WINDOW_NONE && loop_pos != rootWindow()) { | ||||
3302 | X11Client *pos = workspace()->findClient(Predicate::WindowMatch, loop_pos); | ||||
3303 | if (pos == nullptr) | ||||
3304 | break; | ||||
3305 | loop_pos = pos->m_transientForId; | ||||
3306 | if (--count == 0 || pos == this) { | ||||
3307 | qCWarning(KWIN_CORE) << "Client " << this << " caused WM_TRANSIENT_FOR loop." ; | ||||
3308 | new_transient_for = rootWindow(); | ||||
3309 | } | ||||
3310 | } | ||||
3311 | if (new_transient_for != rootWindow() | ||||
3312 | && workspace()->findClient(Predicate::WindowMatch, new_transient_for) == nullptr) { | ||||
3313 | // it's transient for a specific window, but that window is not mapped | ||||
3314 | new_transient_for = rootWindow(); | ||||
3315 | } | ||||
3316 | if (new_property_value != m_originalTransientForId) | ||||
3317 | Xcb::setTransientFor(window(), new_property_value); | ||||
3318 | return new_transient_for; | ||||
3319 | } | ||||
3320 | | ||||
3321 | void X11Client::addTransient(AbstractClient* cl) | ||||
3322 | { | ||||
3323 | AbstractClient::addTransient(cl); | ||||
3324 | if (workspace()->mostRecentlyActivatedClient() == this && cl->isModal()) | ||||
3325 | check_active_modal = true; | ||||
3326 | // qDebug() << "ADDTRANS:" << this << ":" << cl; | ||||
3327 | // qDebug() << kBacktrace(); | ||||
3328 | // for ( auto it = transients_list.begin(); | ||||
3329 | // it != transients_list.end(); | ||||
3330 | // ++it ) | ||||
3331 | // qDebug() << "AT:" << (*it); | ||||
3332 | } | ||||
3333 | | ||||
3334 | void X11Client::removeTransient(AbstractClient* cl) | ||||
3335 | { | ||||
3336 | // qDebug() << "REMOVETRANS:" << this << ":" << cl; | ||||
3337 | // qDebug() << kBacktrace(); | ||||
3338 | // cl is transient for this, but this is going away | ||||
3339 | // make cl group transient | ||||
3340 | AbstractClient::removeTransient(cl); | ||||
3341 | if (cl->transientFor() == this) { | ||||
3342 | if (X11Client *c = dynamic_cast<X11Client *>(cl)) { | ||||
3343 | c->m_transientForId = XCB_WINDOW_NONE; | ||||
3344 | c->setTransientFor(nullptr); // SELI | ||||
3345 | // SELI cl->setTransient( rootWindow()); | ||||
3346 | c->setTransient(XCB_WINDOW_NONE); | ||||
3347 | } | ||||
3348 | } | ||||
3349 | } | ||||
3350 | | ||||
3351 | // A new window has been mapped. Check if it's not a mainwindow for this already existing window. | ||||
3352 | void X11Client::checkTransient(xcb_window_t w) | ||||
3353 | { | ||||
3354 | if (m_originalTransientForId != w) | ||||
3355 | return; | ||||
3356 | w = verifyTransientFor(w, true); | ||||
3357 | setTransient(w); | ||||
3358 | } | ||||
3359 | | ||||
3360 | // returns true if cl is the transient_for window for this client, | ||||
3361 | // or recursively the transient_for window | ||||
3362 | bool X11Client::hasTransient(const AbstractClient* cl, bool indirect) const | ||||
3363 | { | ||||
3364 | if (const X11Client *c = dynamic_cast<const X11Client *>(cl)) { | ||||
3365 | // checkGroupTransients() uses this to break loops, so hasTransient() must detect them | ||||
3366 | QList<const X11Client *> set; | ||||
3367 | return hasTransientInternal(c, indirect, set); | ||||
3368 | } | ||||
3369 | return false; | ||||
3370 | } | ||||
3371 | | ||||
3372 | bool X11Client::hasTransientInternal(const X11Client *cl, bool indirect, QList<const X11Client *> &set) const | ||||
3373 | { | ||||
3374 | if (const X11Client *t = dynamic_cast<const X11Client *>(cl->transientFor())) { | ||||
3375 | if (t == this) | ||||
3376 | return true; | ||||
3377 | if (!indirect) | ||||
3378 | return false; | ||||
3379 | if (set.contains(cl)) | ||||
3380 | return false; | ||||
3381 | set.append(cl); | ||||
3382 | return hasTransientInternal(t, indirect, set); | ||||
3383 | } | ||||
3384 | if (!cl->isTransient()) | ||||
3385 | return false; | ||||
3386 | if (group() != cl->group()) | ||||
3387 | return false; | ||||
3388 | // cl is group transient, search from top | ||||
3389 | if (transients().contains(const_cast< X11Client *>(cl))) | ||||
3390 | return true; | ||||
3391 | if (!indirect) | ||||
3392 | return false; | ||||
3393 | if (set.contains(this)) | ||||
3394 | return false; | ||||
3395 | set.append(this); | ||||
3396 | for (auto it = transients().constBegin(); | ||||
3397 | it != transients().constEnd(); | ||||
3398 | ++it) { | ||||
3399 | const X11Client *c = qobject_cast<const X11Client *>(*it); | ||||
3400 | if (!c) { | ||||
3401 | continue; | ||||
3402 | } | ||||
3403 | if (c->hasTransientInternal(cl, indirect, set)) | ||||
3404 | return true; | ||||
3405 | } | ||||
3406 | return false; | ||||
3407 | } | ||||
3408 | | ||||
3409 | QList<AbstractClient*> X11Client::mainClients() const | ||||
3410 | { | ||||
3411 | if (!isTransient()) | ||||
3412 | return QList<AbstractClient*>(); | ||||
3413 | if (const AbstractClient *t = transientFor()) | ||||
3414 | return QList<AbstractClient*>{const_cast< AbstractClient* >(t)}; | ||||
3415 | QList<AbstractClient*> result; | ||||
3416 | Q_ASSERT(group()); | ||||
3417 | for (auto it = group()->members().constBegin(); | ||||
3418 | it != group()->members().constEnd(); | ||||
3419 | ++it) | ||||
3420 | if ((*it)->hasTransient(this, false)) | ||||
3421 | result.append(*it); | ||||
3422 | return result; | ||||
3423 | } | ||||
3424 | | ||||
3425 | AbstractClient* X11Client::findModal(bool allow_itself) | ||||
3426 | { | ||||
3427 | for (auto it = transients().constBegin(); | ||||
3428 | it != transients().constEnd(); | ||||
3429 | ++it) | ||||
3430 | if (AbstractClient* ret = (*it)->findModal(true)) | ||||
3431 | return ret; | ||||
3432 | if (isModal() && allow_itself) | ||||
3433 | return this; | ||||
3434 | return nullptr; | ||||
3435 | } | ||||
3436 | | ||||
3437 | // X11Client::window_group only holds the contents of the hint, | ||||
3438 | // but it should be used only to find the group, not for anything else | ||||
3439 | // Argument is only when some specific group needs to be set. | ||||
3440 | void X11Client::checkGroup(Group* set_group, bool force) | ||||
3441 | { | ||||
3442 | Group* old_group = in_group; | ||||
3443 | if (old_group != nullptr) | ||||
3444 | old_group->ref(); // turn off automatic deleting | ||||
3445 | if (set_group != nullptr) { | ||||
3446 | if (set_group != in_group) { | ||||
3447 | if (in_group != nullptr) | ||||
3448 | in_group->removeMember(this); | ||||
3449 | in_group = set_group; | ||||
3450 | in_group->addMember(this); | ||||
3451 | } | ||||
3452 | } else if (info->groupLeader() != XCB_WINDOW_NONE) { | ||||
3453 | Group* new_group = workspace()->findGroup(info->groupLeader()); | ||||
3454 | X11Client *t = qobject_cast<X11Client *>(transientFor()); | ||||
3455 | if (t != nullptr && t->group() != new_group) { | ||||
3456 | // move the window to the right group (e.g. a dialog provided | ||||
3457 | // by different app, but transient for this one, so make it part of that group) | ||||
3458 | new_group = t->group(); | ||||
3459 | } | ||||
3460 | if (new_group == nullptr) // doesn't exist yet | ||||
3461 | new_group = new Group(info->groupLeader()); | ||||
3462 | if (new_group != in_group) { | ||||
3463 | if (in_group != nullptr) | ||||
3464 | in_group->removeMember(this); | ||||
3465 | in_group = new_group; | ||||
3466 | in_group->addMember(this); | ||||
3467 | } | ||||
3468 | } else { | ||||
3469 | if (X11Client *t = qobject_cast<X11Client *>(transientFor())) { | ||||
3470 | // doesn't have window group set, but is transient for something | ||||
3471 | // so make it part of that group | ||||
3472 | Group* new_group = t->group(); | ||||
3473 | if (new_group != in_group) { | ||||
3474 | if (in_group != nullptr) | ||||
3475 | in_group->removeMember(this); | ||||
3476 | in_group = t->group(); | ||||
3477 | in_group->addMember(this); | ||||
3478 | } | ||||
3479 | } else if (groupTransient()) { | ||||
3480 | // group transient which actually doesn't have a group :( | ||||
3481 | // try creating group with other windows with the same client leader | ||||
3482 | Group* new_group = workspace()->findClientLeaderGroup(this); | ||||
3483 | if (new_group == nullptr) | ||||
3484 | new_group = new Group(XCB_WINDOW_NONE); | ||||
3485 | if (new_group != in_group) { | ||||
3486 | if (in_group != nullptr) | ||||
3487 | in_group->removeMember(this); | ||||
3488 | in_group = new_group; | ||||
3489 | in_group->addMember(this); | ||||
3490 | } | ||||
3491 | } else { // Not transient without a group, put it in its client leader group. | ||||
3492 | // This might be stupid if grouping was used for e.g. taskbar grouping | ||||
3493 | // or minimizing together the whole group, but as long as it is used | ||||
3494 | // only for dialogs it's better to keep windows from one app in one group. | ||||
3495 | Group* new_group = workspace()->findClientLeaderGroup(this); | ||||
3496 | if (in_group != nullptr && in_group != new_group) { | ||||
3497 | in_group->removeMember(this); | ||||
3498 | in_group = nullptr; | ||||
3499 | } | ||||
3500 | if (new_group == nullptr) | ||||
3501 | new_group = new Group(XCB_WINDOW_NONE); | ||||
3502 | if (in_group != new_group) { | ||||
3503 | in_group = new_group; | ||||
3504 | in_group->addMember(this); | ||||
3505 | } | ||||
3506 | } | ||||
3507 | } | ||||
3508 | if (in_group != old_group || force) { | ||||
3509 | for (auto it = transients().constBegin(); | ||||
3510 | it != transients().constEnd(); | ||||
3511 | ) { | ||||
3512 | auto *c = *it; | ||||
3513 | // group transients in the old group are no longer transient for it | ||||
3514 | if (c->groupTransient() && c->group() != group()) { | ||||
3515 | removeTransientFromList(c); | ||||
3516 | it = transients().constBegin(); // restart, just in case something more has changed with the list | ||||
3517 | } else | ||||
3518 | ++it; | ||||
3519 | } | ||||
3520 | if (groupTransient()) { | ||||
3521 | // no longer transient for ones in the old group | ||||
3522 | if (old_group != nullptr) { | ||||
3523 | for (auto it = old_group->members().constBegin(); | ||||
3524 | it != old_group->members().constEnd(); | ||||
3525 | ++it) | ||||
3526 | (*it)->removeTransient(this); | ||||
3527 | } | ||||
3528 | // and make transient for all in the new group | ||||
3529 | for (auto it = group()->members().constBegin(); | ||||
3530 | it != group()->members().constEnd(); | ||||
3531 | ++it) { | ||||
3532 | if (*it == this) | ||||
3533 | break; // this means the window is only transient for windows mapped before it | ||||
3534 | (*it)->addTransient(this); | ||||
3535 | } | ||||
3536 | } | ||||
3537 | // group transient splashscreens should be transient even for windows | ||||
3538 | // in group mapped later | ||||
3539 | for (auto it = group()->members().constBegin(); | ||||
3540 | it != group()->members().constEnd(); | ||||
3541 | ++it) { | ||||
3542 | if (!(*it)->isSplash()) | ||||
3543 | continue; | ||||
3544 | if (!(*it)->groupTransient()) | ||||
3545 | continue; | ||||
3546 | if (*it == this || hasTransient(*it, true)) // TODO indirect? | ||||
3547 | continue; | ||||
3548 | addTransient(*it); | ||||
3549 | } | ||||
3550 | } | ||||
3551 | if (old_group != nullptr) | ||||
3552 | old_group->deref(); // can be now deleted if empty | ||||
3553 | checkGroupTransients(); | ||||
3554 | checkActiveModal(); | ||||
3555 | workspace()->updateClientLayer(this); | ||||
3556 | } | ||||
3557 | | ||||
3558 | // used by Workspace::findClientLeaderGroup() | ||||
3559 | void X11Client::changeClientLeaderGroup(Group* gr) | ||||
3560 | { | ||||
3561 | // transientFor() != NULL are in the group of their mainwindow, so keep them there | ||||
3562 | if (transientFor() != nullptr) | ||||
3563 | return; | ||||
3564 | // also don't change the group for window which have group set | ||||
3565 | if (info->groupLeader()) | ||||
3566 | return; | ||||
3567 | checkGroup(gr); // change group | ||||
3568 | } | ||||
3569 | | ||||
3570 | bool X11Client::check_active_modal = false; | ||||
3571 | | ||||
3572 | void X11Client::checkActiveModal() | ||||
3573 | { | ||||
3574 | // if the active window got new modal transient, activate it. | ||||
3575 | // cannot be done in AddTransient(), because there may temporarily | ||||
3576 | // exist loops, breaking findModal | ||||
3577 | X11Client *check_modal = dynamic_cast<X11Client *>(workspace()->mostRecentlyActivatedClient()); | ||||
3578 | if (check_modal != nullptr && check_modal->check_active_modal) { | ||||
3579 | X11Client *new_modal = dynamic_cast<X11Client *>(check_modal->findModal()); | ||||
3580 | if (new_modal != nullptr && new_modal != check_modal) { | ||||
3581 | if (!new_modal->isManaged()) | ||||
3582 | return; // postpone check until end of manage() | ||||
3583 | workspace()->activateClient(new_modal); | ||||
3584 | } | ||||
3585 | check_modal->check_active_modal = false; | ||||
3586 | } | ||||
3587 | } | ||||
3588 | | ||||
2946 | } // namespace | 3589 | } // namespace | ||
2947 | 3590 | |