7ccedfdbb5bafcb2762f34e9ceaaf678632f26f5
[gpgol.git] / src / explorer-events.cpp
1 /* explorer-events.cpp - Event handling for the application.
2  * Copyright (C) 2016 by Bundesamt für Sicherheit in der Informationstechnik
3  * Software engineering by Intevation GmbH
4  *
5  * This file is part of GpgOL.
6  *
7  * GpgOL is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * GpgOL is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this program; if not, see <http://www.gnu.org/licenses/>.
19  */
20
21 /* The event handler classes defined in this file follow the
22    general pattern that they implment the IDispatch interface
23    through the eventsink macros and handle event invocations
24    in their invoke methods.
25 */
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29
30 #include "eventsink.h"
31 #include "ocidl.h"
32 #include "common.h"
33 #include "oomhelp.h"
34 #include "mail.h"
35 #include "gpgoladdin.h"
36 #include "windowmessages.h"
37
38 /* Explorer Events */
39 BEGIN_EVENT_SINK(ExplorerEvents, IDispatch)
40 EVENT_SINK_DEFAULT_CTOR(ExplorerEvents)
41 EVENT_SINK_DEFAULT_DTOR(ExplorerEvents)
42 typedef enum
43   {
44     Activate = 0xF001,
45     AttachmentSelectionChange = 0xFC79,
46     BeforeFolderSwitch = 0xF003,
47     BeforeItemCopy = 0xFA0E,
48     BeforeItemCut = 0xFA0F,
49     BeforeItemPaste = 0xFA10,
50     BeforeMaximize = 0xFA11,
51     BeforeMinimize = 0xFA12,
52     BeforeMove = 0xFA13,
53     BeforeSize = 0xFA14,
54     BeforeViewSwitch = 0xF005,
55     Close = 0xF008,
56     Deactivate = 0xF006,
57     DisplayModeChange = 0xFC98,
58     FolderSwitch = 0xF002,
59     InlineResponse = 0xFC92,
60     InlineResponseClose = 0xFC96,
61     SelectionChange = 0xF007,
62     ViewSwitch = 0xF004
63   } ExplorerEvent;
64
65 /*
66    We need to avoid UI invalidations as much as possible as invalidations
67    can trigger reloads of mails and at a bad time can crash us.
68
69    So we only invalidate the UI after we have handled the read event of
70    a mail and again after decrypt / verify.
71
72    The problem is that we also need to update the UI when mails are
73    unselected so we don't show "Secure" if nothing is selected.
74
75    On a switch from one Mail to another we see two selection changes.
76    One for the unselect the other for the select immediately after
77    each other.
78
79    When we just have an unselect we see only one selection change.
80
81    So after we detect the unselect we switch the state in our
82    explorerMap to unselect seen and start a WatchDog thread.
83
84    That thread sleeps for 500ms and then checks if the state
85    was switched to select seen in the meantime. If
86    not it triggers the UI Invalidation in the GUI thread.
87    */
88 #include <map>
89
90 typedef enum
91   {
92     WatchDogActive = 0x01,
93     UnselectSeen = 0x02,
94     SelectSeen = 0x04,
95   } SelectionState;
96
97 std::map<LPDISPATCH, int> s_explorerMap;
98
99 gpgrt_lock_t explorer_map_lock = GPGRT_LOCK_INITIALIZER;
100
101 static bool
102 hasSelection (LPDISPATCH explorer)
103 {
104   LPDISPATCH selection = get_oom_object (explorer, "Selection");
105
106   if (!selection)
107     {
108       TRACEPOINT;
109       return false;
110     }
111
112   int count = get_oom_int (selection, "Count");
113   gpgol_release (selection);
114
115   if (count)
116     {
117       return true;
118     }
119   return false;
120 }
121
122 static DWORD WINAPI
123 start_watchdog (LPVOID arg)
124 {
125   LPDISPATCH explorer = (LPDISPATCH) arg;
126
127   Sleep (500);
128   gpgrt_lock_lock (&explorer_map_lock);
129
130   auto it = s_explorerMap.find (explorer);
131
132   if (it == s_explorerMap.end ())
133     {
134       log_error ("%s:%s: Watchdog for unknwon explorer %p",
135                  SRCNAME, __func__, explorer);
136       gpgrt_lock_unlock (&explorer_map_lock);
137       return 0;
138     }
139
140   if ((it->second & SelectSeen))
141     {
142       log_oom ("%s:%s: Cancel watchdog as we have seen a select %p",
143                      SRCNAME, __func__, explorer);
144       it->second = SelectSeen;
145     }
146   else if ((it->second & UnselectSeen))
147     {
148       log_debug ("%s:%s: Deteced unselect invalidating UI.",
149                  SRCNAME, __func__);
150       it->second = UnselectSeen;
151       gpgrt_lock_unlock (&explorer_map_lock);
152       do_in_ui_thread (INVALIDATE_UI, nullptr);
153       return 0;
154     }
155   gpgrt_lock_unlock (&explorer_map_lock);
156
157   return 0;
158 }
159
160 static void
161 changeSeen (LPDISPATCH explorer)
162 {
163   gpgrt_lock_lock (&explorer_map_lock);
164
165   auto it = s_explorerMap.find (explorer);
166
167   if (it == s_explorerMap.end ())
168     {
169       it = s_explorerMap.insert (std::make_pair (explorer, 0)).first;
170     }
171
172   auto state = it->second;
173   bool has_selection = hasSelection (explorer);
174
175   if (has_selection)
176     {
177       it->second = (state & WatchDogActive) + SelectSeen;
178       log_oom ("%s:%s: Seen select for %p",
179                      SRCNAME, __func__, explorer);
180     }
181   else
182     {
183       if ((it->second & WatchDogActive))
184         {
185           log_oom ("%s:%s: Seen unselect for %p but watchdog exists.",
186                          SRCNAME, __func__, explorer);
187         }
188       else
189         {
190           CloseHandle (CreateThread (NULL, 0, start_watchdog, (LPVOID) explorer,
191                                      0, NULL));
192         }
193       it->second = UnselectSeen + WatchDogActive;
194     }
195   gpgrt_lock_unlock (&explorer_map_lock);
196 }
197
198 EVENT_SINK_INVOKE(ExplorerEvents)
199 {
200   USE_INVOKE_ARGS
201   switch(dispid)
202     {
203       case SelectionChange:
204         {
205           log_oom ("%s:%s: Selection change in explorer: %p",
206                          SRCNAME, __func__, this);
207           changeSeen (m_object);
208           break;
209         }
210       case Close:
211         {
212           log_oom ("%s:%s: Deleting event handler: %p",
213                          SRCNAME, __func__, this);
214
215           GpgolAddin::get_instance ()->unregisterExplorerSink (this);
216           gpgrt_lock_lock (&explorer_map_lock);
217           s_explorerMap.erase (m_object);
218           gpgrt_lock_unlock (&explorer_map_lock);
219           delete this;
220           return S_OK;
221         }
222       default:
223         break;
224 #if 0
225         log_oom ("%s:%s: Unhandled Event: %lx \n",
226                        SRCNAME, __func__, dispid);
227 #endif
228     }
229   return S_OK;
230 }
231 END_EVENT_SINK(ExplorerEvents, IID_ExplorerEvents)