Changeset View
Changeset View
Standalone View
Standalone View
activation.cpp
Show All 20 Lines | |||||
21 | 21 | | |||
22 | /* | 22 | /* | ||
23 | 23 | | |||
24 | This file contains things relevant to window activation and focus | 24 | This file contains things relevant to window activation and focus | ||
25 | stealing prevention. | 25 | stealing prevention. | ||
26 | 26 | | |||
27 | */ | 27 | */ | ||
28 | 28 | | |||
29 | #include "client.h" | 29 | #include "x11client.h" | ||
30 | #include "cursor.h" | 30 | #include "cursor.h" | ||
31 | #include "focuschain.h" | 31 | #include "focuschain.h" | ||
32 | #include "netinfo.h" | 32 | #include "netinfo.h" | ||
33 | #include "workspace.h" | 33 | #include "workspace.h" | ||
34 | #ifdef KWIN_BUILD_ACTIVITIES | 34 | #ifdef KWIN_BUILD_ACTIVITIES | ||
35 | #include "activities.h" | 35 | #include "activities.h" | ||
36 | #endif | 36 | #endif | ||
37 | 37 | | |||
▲ Show 20 Lines • Show All 49 Lines • ▼ Show 20 Line(s) | 86 | Workspace::allowClientActivation(). | |||
87 | Following checks need to compare times. One time is the timestamp | 87 | Following checks need to compare times. One time is the timestamp | ||
88 | of last user action in the currently active window, the other time is | 88 | of last user action in the currently active window, the other time is | ||
89 | the timestamp of the action that originally caused mapping of the new window | 89 | the timestamp of the action that originally caused mapping of the new window | ||
90 | (e.g. when the application was started). If the first time is newer than | 90 | (e.g. when the application was started). If the first time is newer than | ||
91 | the second one, the window will not be activated, as that indicates | 91 | the second one, the window will not be activated, as that indicates | ||
92 | futher user actions took place after the action leading to this new | 92 | futher user actions took place after the action leading to this new | ||
93 | mapped window. This check is done by Workspace::allowClientActivation(). | 93 | mapped window. This check is done by Workspace::allowClientActivation(). | ||
94 | There are several ways how to get the timestamp of action that caused | 94 | There are several ways how to get the timestamp of action that caused | ||
95 | the new mapped window (done in Client::readUserTimeMapTimestamp()) : | 95 | the new mapped window (done in X11Client::readUserTimeMapTimestamp()) : | ||
96 | - the window may have the _NET_WM_USER_TIME property. This way | 96 | - the window may have the _NET_WM_USER_TIME property. This way | ||
97 | the application may either explicitly request that the window is not | 97 | the application may either explicitly request that the window is not | ||
98 | activated (by using 0 timestamp), or the property contains the time | 98 | activated (by using 0 timestamp), or the property contains the time | ||
99 | of last user action in the application. | 99 | of last user action in the application. | ||
100 | - KWin itself tries to detect time of last user action in every window, | 100 | - KWin itself tries to detect time of last user action in every window, | ||
101 | by watching KeyPress and ButtonPress events on windows. This way some | 101 | by watching KeyPress and ButtonPress events on windows. This way some | ||
102 | events may be missed (if they don't propagate to the toplevel window), | 102 | events may be missed (if they don't propagate to the toplevel window), | ||
103 | but it's good as a fallback for applications that don't provide | 103 | but it's good as a fallback for applications that don't provide | ||
Show All 22 Lines | 117 | - as the last resort, the _KDE_NET_USER_CREATION_TIME timestamp | |||
126 | the timestamp empty for next mapping of the window. In the sooner | 126 | the timestamp empty for next mapping of the window. In the sooner | ||
127 | case, the timestamp will be used. This helps in case when | 127 | case, the timestamp will be used. This helps in case when | ||
128 | an application is launched without application startup notification, | 128 | an application is launched without application startup notification, | ||
129 | it creates its mainwindow, and starts its initialization (that | 129 | it creates its mainwindow, and starts its initialization (that | ||
130 | may possibly take long time). The timestamp used will be older | 130 | may possibly take long time). The timestamp used will be older | ||
131 | than any user action done after launching this application. | 131 | than any user action done after launching this application. | ||
132 | - if no timestamp is found at all, the window is activated. | 132 | - if no timestamp is found at all, the window is activated. | ||
133 | The check whether two windows belong to the same application (same | 133 | The check whether two windows belong to the same application (same | ||
134 | process) is done in Client::belongToSameApplication(). Not 100% reliable, | 134 | process) is done in X11Client::belongToSameApplication(). Not 100% reliable, | ||
135 | but hopefully 99,99% reliable. | 135 | but hopefully 99,99% reliable. | ||
136 | 136 | | |||
137 | As a somewhat special case, window activation is always enabled when | 137 | As a somewhat special case, window activation is always enabled when | ||
138 | session saving is in progress. When session saving, the session | 138 | session saving is in progress. When session saving, the session | ||
139 | manager allows only one application to interact with the user. | 139 | manager allows only one application to interact with the user. | ||
140 | Not allowing window activation in such case would result in e.g. dialogs | 140 | Not allowing window activation in such case would result in e.g. dialogs | ||
141 | not becoming active, so focus stealing prevention would cause here | 141 | not becoming active, so focus stealing prevention would cause here | ||
142 | more harm than good. | 142 | more harm than good. | ||
Show All 22 Lines | 164 | - konqueror reusing, i.e. kfmclient tells running Konqueror instance | |||
165 | to open new window | 165 | to open new window | ||
166 | - without focus stealing prevention - no problem | 166 | - without focus stealing prevention - no problem | ||
167 | - with ASN (application startup notification) - ASN is forwarded, | 167 | - with ASN (application startup notification) - ASN is forwarded, | ||
168 | and because it's newer than the instance's user timestamp, | 168 | and because it's newer than the instance's user timestamp, | ||
169 | it takes precedence | 169 | it takes precedence | ||
170 | - without ASN - user timestamp needs to be reset, otherwise it would | 170 | - without ASN - user timestamp needs to be reset, otherwise it would | ||
171 | be used, and it's old; moreover this new window mustn't be detected | 171 | be used, and it's old; moreover this new window mustn't be detected | ||
172 | as window belonging to already running application, or it wouldn't | 172 | as window belonging to already running application, or it wouldn't | ||
173 | be activated - see Client::sameAppWindowRoleMatch() for the (rather ugly) | 173 | be activated - see X11Client::sameAppWindowRoleMatch() for the (rather ugly) | ||
174 | hack | 174 | hack | ||
175 | - konqueror preloading, i.e. window is created in advance, and kfmclient | 175 | - konqueror preloading, i.e. window is created in advance, and kfmclient | ||
176 | tells this Konqueror instance to show it later | 176 | tells this Konqueror instance to show it later | ||
177 | - without focus stealing prevention - no problem | 177 | - without focus stealing prevention - no problem | ||
178 | - with ASN - ASN is forwarded, and because it's newer than the instance's | 178 | - with ASN - ASN is forwarded, and because it's newer than the instance's | ||
179 | user timestamp, it takes precedence | 179 | user timestamp, it takes precedence | ||
180 | - without ASN - user timestamp needs to be reset, otherwise it would | 180 | - without ASN - user timestamp needs to be reset, otherwise it would | ||
181 | be used, and it's old; also, creation timestamp is changed to | 181 | be used, and it's old; also, creation timestamp is changed to | ||
▲ Show 20 Lines • Show All 139 Lines • ▼ Show 20 Line(s) | 320 | if (options->focusPolicyIsReasonable() || force) | |||
321 | requestFocus(c, force); | 321 | requestFocus(c, force); | ||
322 | 322 | | |||
323 | // Don't update user time for clients that have focus stealing workaround. | 323 | // Don't update user time for clients that have focus stealing workaround. | ||
324 | // As they usually belong to the current active window but fail to provide | 324 | // As they usually belong to the current active window but fail to provide | ||
325 | // this information, updating their user time would make the user time | 325 | // this information, updating their user time would make the user time | ||
326 | // of the currently active window old, and reject further activation for it. | 326 | // of the currently active window old, and reject further activation for it. | ||
327 | // E.g. typing URL in minicli which will show kio_uiserver dialog (with workaround), | 327 | // E.g. typing URL in minicli which will show kio_uiserver dialog (with workaround), | ||
328 | // and then kdesktop shows dialog about SSL certificate. | 328 | // and then kdesktop shows dialog about SSL certificate. | ||
329 | // This needs also avoiding user creation time in Client::readUserTimeMapTimestamp(). | 329 | // This needs also avoiding user creation time in X11Client::readUserTimeMapTimestamp(). | ||
330 | if (Client *client = dynamic_cast<Client*>(c)) { | 330 | if (X11Client *client = dynamic_cast<X11Client *>(c)) { | ||
331 | // updateUserTime is X11 specific | 331 | // updateUserTime is X11 specific | ||
332 | client->updateUserTime(); | 332 | client->updateUserTime(); | ||
333 | } | 333 | } | ||
334 | } | 334 | } | ||
335 | 335 | | |||
336 | /** | 336 | /** | ||
337 | * Tries to activate the client by asking X for the input focus. This | 337 | * Tries to activate the client by asking X for the input focus. This | ||
338 | * function does not perform any show, raise or desktop switching. See | 338 | * function does not perform any show, raise or desktop switching. See | ||
▲ Show 20 Lines • Show All 351 Lines • ▼ Show 20 Line(s) | |||||
690 | //******************************************** | 690 | //******************************************** | ||
691 | 691 | | |||
692 | /** | 692 | /** | ||
693 | * Updates the user time (time of last action in the active window). | 693 | * Updates the user time (time of last action in the active window). | ||
694 | * This is called inside kwin for every action with the window | 694 | * This is called inside kwin for every action with the window | ||
695 | * that qualifies for user interaction (clicking on it, activate it | 695 | * that qualifies for user interaction (clicking on it, activate it | ||
696 | * externally, etc.). | 696 | * externally, etc.). | ||
697 | */ | 697 | */ | ||
698 | void Client::updateUserTime(xcb_timestamp_t time) | 698 | void X11Client::updateUserTime(xcb_timestamp_t time) | ||
699 | { | 699 | { | ||
700 | // copied in Group::updateUserTime | 700 | // copied in Group::updateUserTime | ||
701 | if (time == XCB_TIME_CURRENT_TIME) { | 701 | if (time == XCB_TIME_CURRENT_TIME) { | ||
702 | updateXTime(); | 702 | updateXTime(); | ||
703 | time = xTime(); | 703 | time = xTime(); | ||
704 | } | 704 | } | ||
705 | if (time != -1U | 705 | if (time != -1U | ||
706 | && (m_userTime == XCB_TIME_CURRENT_TIME | 706 | && (m_userTime == XCB_TIME_CURRENT_TIME | ||
707 | || NET::timestampCompare(time, m_userTime) > 0)) { // time > user_time | 707 | || NET::timestampCompare(time, m_userTime) > 0)) { // time > user_time | ||
708 | m_userTime = time; | 708 | m_userTime = time; | ||
709 | shade_below = nullptr; // do not hover re-shade a window after it got interaction | 709 | shade_below = nullptr; // do not hover re-shade a window after it got interaction | ||
710 | } | 710 | } | ||
711 | group()->updateUserTime(m_userTime); | 711 | group()->updateUserTime(m_userTime); | ||
712 | } | 712 | } | ||
713 | 713 | | |||
714 | xcb_timestamp_t Client::readUserCreationTime() const | 714 | xcb_timestamp_t X11Client::readUserCreationTime() const | ||
715 | { | 715 | { | ||
716 | Xcb::Property prop(false, window(), atoms->kde_net_wm_user_creation_time, XCB_ATOM_CARDINAL, 0, 1); | 716 | Xcb::Property prop(false, window(), atoms->kde_net_wm_user_creation_time, XCB_ATOM_CARDINAL, 0, 1); | ||
717 | return prop.value<xcb_timestamp_t>(-1); | 717 | return prop.value<xcb_timestamp_t>(-1); | ||
718 | } | 718 | } | ||
719 | 719 | | |||
720 | xcb_timestamp_t Client::readUserTimeMapTimestamp(const KStartupInfoId *asn_id, const KStartupInfoData *asn_data, | 720 | xcb_timestamp_t X11Client::readUserTimeMapTimestamp(const KStartupInfoId *asn_id, const KStartupInfoData *asn_data, | ||
721 | bool session) const | 721 | bool session) const | ||
722 | { | 722 | { | ||
723 | xcb_timestamp_t time = info->userTime(); | 723 | xcb_timestamp_t time = info->userTime(); | ||
724 | //qDebug() << "User timestamp, initial:" << time; | 724 | //qDebug() << "User timestamp, initial:" << time; | ||
725 | //^^ this deadlocks kwin --replace sometimes. | 725 | //^^ this deadlocks kwin --replace sometimes. | ||
726 | 726 | | |||
727 | // newer ASN timestamp always replaces user timestamp, unless user timestamp is 0 | 727 | // newer ASN timestamp always replaces user timestamp, unless user timestamp is 0 | ||
728 | // helps e.g. with konqy reusing | 728 | // helps e.g. with konqy reusing | ||
729 | if (asn_data != nullptr && time != 0) { | 729 | if (asn_data != nullptr && time != 0) { | ||
730 | if (asn_id->timestamp() != 0 | 730 | if (asn_id->timestamp() != 0 | ||
731 | && (time == -1U || NET::timestampCompare(asn_id->timestamp(), time) > 0)) { | 731 | && (time == -1U || NET::timestampCompare(asn_id->timestamp(), time) > 0)) { | ||
732 | time = asn_id->timestamp(); | 732 | time = asn_id->timestamp(); | ||
733 | } | 733 | } | ||
734 | } | 734 | } | ||
735 | qCDebug(KWIN_CORE) << "User timestamp, ASN:" << time; | 735 | qCDebug(KWIN_CORE) << "User timestamp, ASN:" << time; | ||
736 | if (time == -1U) { | 736 | if (time == -1U) { | ||
737 | // The window doesn't have any timestamp. | 737 | // The window doesn't have any timestamp. | ||
738 | // If it's the first window for its application | 738 | // If it's the first window for its application | ||
739 | // (i.e. there's no other window from the same app), | 739 | // (i.e. there's no other window from the same app), | ||
740 | // use the _KDE_NET_WM_USER_CREATION_TIME trick. | 740 | // use the _KDE_NET_WM_USER_CREATION_TIME trick. | ||
741 | // Otherwise, refuse activation of a window | 741 | // Otherwise, refuse activation of a window | ||
742 | // from already running application if this application | 742 | // from already running application if this application | ||
743 | // is not the active one (unless focus stealing prevention is turned off). | 743 | // is not the active one (unless focus stealing prevention is turned off). | ||
744 | Client* act = dynamic_cast<Client*>(workspace()->mostRecentlyActivatedClient()); | 744 | X11Client *act = dynamic_cast<X11Client *>(workspace()->mostRecentlyActivatedClient()); | ||
745 | if (act != nullptr && !belongToSameApplication(act, this, SameApplicationCheck::RelaxedForActive)) { | 745 | if (act != nullptr && !belongToSameApplication(act, this, SameApplicationCheck::RelaxedForActive)) { | ||
746 | bool first_window = true; | 746 | bool first_window = true; | ||
747 | auto sameApplicationActiveHackPredicate = [this](const Client *cl) { | 747 | auto sameApplicationActiveHackPredicate = [this](const X11Client *cl) { | ||
748 | // ignore already existing splashes, toolbars, utilities and menus, | 748 | // ignore already existing splashes, toolbars, utilities and menus, | ||
749 | // as the app may show those before the main window | 749 | // as the app may show those before the main window | ||
750 | return !cl->isSplash() && !cl->isToolbar() && !cl->isUtility() && !cl->isMenu() | 750 | return !cl->isSplash() && !cl->isToolbar() && !cl->isUtility() && !cl->isMenu() | ||
751 | && cl != this && Client::belongToSameApplication(cl, this, SameApplicationCheck::RelaxedForActive); | 751 | && cl != this && X11Client::belongToSameApplication(cl, this, SameApplicationCheck::RelaxedForActive); | ||
752 | }; | 752 | }; | ||
753 | if (isTransient()) { | 753 | if (isTransient()) { | ||
754 | auto clientMainClients = [this] () -> ClientList { | 754 | auto clientMainClients = [this] () -> ClientList { | ||
755 | ClientList ret; | 755 | ClientList ret; | ||
756 | const auto mcs = mainClients(); | 756 | const auto mcs = mainClients(); | ||
757 | for (auto mc: mcs) { | 757 | for (auto mc: mcs) { | ||
758 | if (Client *c = dynamic_cast<Client*>(mc)) { | 758 | if (X11Client *c = dynamic_cast<X11Client *>(mc)) { | ||
759 | ret << c; | 759 | ret << c; | ||
760 | } | 760 | } | ||
761 | } | 761 | } | ||
762 | return ret; | 762 | return ret; | ||
763 | }; | 763 | }; | ||
764 | if (act->hasTransient(this, true)) | 764 | if (act->hasTransient(this, true)) | ||
765 | ; // is transient for currently active window, even though it's not | 765 | ; // is transient for currently active window, even though it's not | ||
766 | // the same app (e.g. kcookiejar dialog) -> allow activation | 766 | // the same app (e.g. kcookiejar dialog) -> allow activation | ||
767 | else if (groupTransient() && | 767 | else if (groupTransient() && | ||
768 | findInList<Client, Client>(clientMainClients(), sameApplicationActiveHackPredicate) == nullptr) | 768 | findInList<X11Client, X11Client>(clientMainClients(), sameApplicationActiveHackPredicate) == nullptr) | ||
769 | ; // standalone transient | 769 | ; // standalone transient | ||
770 | else | 770 | else | ||
771 | first_window = false; | 771 | first_window = false; | ||
772 | } else { | 772 | } else { | ||
773 | if (workspace()->findClient(sameApplicationActiveHackPredicate)) | 773 | if (workspace()->findClient(sameApplicationActiveHackPredicate)) | ||
774 | first_window = false; | 774 | first_window = false; | ||
775 | } | 775 | } | ||
776 | // don't refuse if focus stealing prevention is turned off | 776 | // don't refuse if focus stealing prevention is turned off | ||
Show All 14 Lines | |||||
791 | if (session) | 791 | if (session) | ||
792 | return -1U; | 792 | return -1U; | ||
793 | time = readUserCreationTime(); | 793 | time = readUserCreationTime(); | ||
794 | } | 794 | } | ||
795 | qCDebug(KWIN_CORE) << "User timestamp, final:" << this << ":" << time; | 795 | qCDebug(KWIN_CORE) << "User timestamp, final:" << this << ":" << time; | ||
796 | return time; | 796 | return time; | ||
797 | } | 797 | } | ||
798 | 798 | | |||
799 | xcb_timestamp_t Client::userTime() const | 799 | xcb_timestamp_t X11Client::userTime() const | ||
800 | { | 800 | { | ||
801 | xcb_timestamp_t time = m_userTime; | 801 | xcb_timestamp_t time = m_userTime; | ||
802 | if (time == 0) // doesn't want focus after showing | 802 | if (time == 0) // doesn't want focus after showing | ||
803 | return 0; | 803 | return 0; | ||
804 | Q_ASSERT(group() != nullptr); | 804 | Q_ASSERT(group() != nullptr); | ||
805 | if (time == -1U | 805 | if (time == -1U | ||
806 | || (group()->userTime() != -1U | 806 | || (group()->userTime() != -1U | ||
807 | && NET::timestampCompare(group()->userTime(), time) > 0)) | 807 | && NET::timestampCompare(group()->userTime(), time) > 0)) | ||
808 | time = group()->userTime(); | 808 | time = group()->userTime(); | ||
809 | return time; | 809 | return time; | ||
810 | } | 810 | } | ||
811 | 811 | | |||
812 | void Client::doSetActive() | 812 | void X11Client::doSetActive() | ||
813 | { | 813 | { | ||
814 | updateUrgency(); // demand attention again if it's still urgent | 814 | updateUrgency(); // demand attention again if it's still urgent | ||
815 | info->setState(isActive() ? NET::Focused : NET::States(), NET::Focused); | 815 | info->setState(isActive() ? NET::Focused : NET::States(), NET::Focused); | ||
816 | } | 816 | } | ||
817 | 817 | | |||
818 | void Client::startupIdChanged() | 818 | void X11Client::startupIdChanged() | ||
819 | { | 819 | { | ||
820 | KStartupInfoId asn_id; | 820 | KStartupInfoId asn_id; | ||
821 | KStartupInfoData asn_data; | 821 | KStartupInfoData asn_data; | ||
822 | bool asn_valid = workspace()->checkStartupNotification(window(), asn_id, asn_data); | 822 | bool asn_valid = workspace()->checkStartupNotification(window(), asn_id, asn_data); | ||
823 | if (!asn_valid) | 823 | if (!asn_valid) | ||
824 | return; | 824 | return; | ||
825 | // If the ASN contains desktop, move it to the desktop, otherwise move it to the current | 825 | // If the ASN contains desktop, move it to the desktop, otherwise move it to the current | ||
826 | // desktop (since the new ASN should make the window act like if it's a new application | 826 | // desktop (since the new ASN should make the window act like if it's a new application | ||
Show All 12 Lines | 838 | if (asn_data.desktop() != 0 && !isOnCurrentDesktop()) | |||
839 | activate = false; // it was started on different desktop than current one | 839 | activate = false; // it was started on different desktop than current one | ||
840 | if (activate) | 840 | if (activate) | ||
841 | workspace()->activateClient(this); | 841 | workspace()->activateClient(this); | ||
842 | else | 842 | else | ||
843 | demandAttention(); | 843 | demandAttention(); | ||
844 | } | 844 | } | ||
845 | } | 845 | } | ||
846 | 846 | | |||
847 | void Client::updateUrgency() | 847 | void X11Client::updateUrgency() | ||
848 | { | 848 | { | ||
849 | if (info->urgency()) | 849 | if (info->urgency()) | ||
850 | demandAttention(); | 850 | demandAttention(); | ||
851 | } | 851 | } | ||
852 | 852 | | |||
853 | //**************************************** | 853 | //**************************************** | ||
854 | // Group | 854 | // Group | ||
855 | //**************************************** | 855 | //**************************************** | ||
856 | 856 | | |||
857 | void Group::startupIdChanged() | 857 | void Group::startupIdChanged() | ||
858 | { | 858 | { | ||
859 | KStartupInfoId asn_id; | 859 | KStartupInfoId asn_id; | ||
860 | KStartupInfoData asn_data; | 860 | KStartupInfoData asn_data; | ||
861 | bool asn_valid = workspace()->checkStartupNotification(leader_wid, asn_id, asn_data); | 861 | bool asn_valid = workspace()->checkStartupNotification(leader_wid, asn_id, asn_data); | ||
862 | if (!asn_valid) | 862 | if (!asn_valid) | ||
863 | return; | 863 | return; | ||
864 | if (asn_id.timestamp() != 0 && user_time != -1U | 864 | if (asn_id.timestamp() != 0 && user_time != -1U | ||
865 | && NET::timestampCompare(asn_id.timestamp(), user_time) > 0) { | 865 | && NET::timestampCompare(asn_id.timestamp(), user_time) > 0) { | ||
866 | user_time = asn_id.timestamp(); | 866 | user_time = asn_id.timestamp(); | ||
867 | } | 867 | } | ||
868 | } | 868 | } | ||
869 | 869 | | |||
870 | void Group::updateUserTime(xcb_timestamp_t time) | 870 | void Group::updateUserTime(xcb_timestamp_t time) | ||
871 | { | 871 | { | ||
872 | // copy of Client::updateUserTime | 872 | // copy of X11Client::updateUserTime | ||
873 | if (time == XCB_CURRENT_TIME) { | 873 | if (time == XCB_CURRENT_TIME) { | ||
874 | updateXTime(); | 874 | updateXTime(); | ||
875 | time = xTime(); | 875 | time = xTime(); | ||
876 | } | 876 | } | ||
877 | if (time != -1U | 877 | if (time != -1U | ||
878 | && (user_time == XCB_CURRENT_TIME | 878 | && (user_time == XCB_CURRENT_TIME | ||
879 | || NET::timestampCompare(time, user_time) > 0)) // time > user_time | 879 | || NET::timestampCompare(time, user_time) > 0)) // time > user_time | ||
880 | user_time = time; | 880 | user_time = time; | ||
881 | } | 881 | } | ||
882 | 882 | | |||
883 | } // namespace | 883 | } // namespace |