Coverage Report

Created: 2026-11-06 11:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/sfx2/source/view/lokhelper.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 */
9
10
#include <sal/config.h>
11
12
#include <string>
13
#include <string_view>
14
#include <list>
15
#include <mutex>
16
17
#include <sfx2/lokcomponenthelpers.hxx>
18
#include <sfx2/lokhelper.hxx>
19
#include <sfx2/lokunocmdlist.hxx>
20
21
#include <com/sun/star/frame/Desktop.hpp>
22
#include <com/sun/star/ui/ContextChangeEventObject.hpp>
23
#include <com/sun/star/xml/crypto/SEInitializer.hpp>
24
#include <com/sun/star/xml/crypto/XCertificateCreator.hpp>
25
26
#include <comphelper/dispatchcommand.hxx>
27
#include <comphelper/JsonToPropertyValues_with_boost.hxx>
28
#include <comphelper/processfactory.hxx>
29
#include <comphelper/propertyvalue.hxx>
30
#include <comphelper/sequence.hxx>
31
#include <o3tl/string_view.hxx>
32
#include <rtl/strbuf.hxx>
33
#include <vcl/lok.hxx>
34
#include <vcl/svapp.hxx>
35
#include <vcl/commandevent.hxx>
36
#include <vcl/vclevent.hxx>
37
#include <vcl/window.hxx>
38
#include <sal/log.hxx>
39
#include <sfx2/app.hxx>
40
#include <sfx2/docfile.hxx>
41
#include <sfx2/msg.hxx>
42
#include <sfx2/viewsh.hxx>
43
#include <sfx2/request.hxx>
44
#include <sfx2/sfxsids.hrc>
45
#include <sfx2/viewfrm.hxx>
46
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
47
#include <comphelper/lok.hxx>
48
#include <sfx2/msgpool.hxx>
49
#include <comphelper/scopeguard.hxx>
50
#include <comphelper/base64.hxx>
51
#include <tools/json_writer.hxx>
52
#include <svl/cryptosign.hxx>
53
#include <tools/urlobj.hxx>
54
55
#include <boost/property_tree/json_parser.hpp>
56
57
using namespace com::sun::star;
58
59
namespace {
60
bool g_bSettingView(false);
61
62
/// Used to disable callbacks.
63
/// Needed to avoid recursion when switching views,
64
/// which can cause clients to invoke LOKit API and
65
/// implicitly set the view, which might cause an
66
/// infinite recursion if not detected and prevented.
67
class DisableCallbacks
68
{
69
public:
70
    DisableCallbacks()
71
0
    {
72
0
        assert(m_nDisabled >= 0 && "Expected non-negative DisabledCallbacks state when disabling.");
73
0
        ++m_nDisabled;
74
0
    }
75
76
    ~DisableCallbacks()
77
0
    {
78
0
        assert(m_nDisabled > 0 && "Expected positive DisabledCallbacks state when re-enabling.");
79
0
        --m_nDisabled;
80
0
    }
81
82
    static inline bool disabled()
83
18.6k
    {
84
18.6k
        return !comphelper::LibreOfficeKit::isActive() || m_nDisabled != 0;
85
18.6k
    }
86
87
private:
88
    static int m_nDisabled;
89
};
90
91
int DisableCallbacks::m_nDisabled = 0;
92
}
93
94
namespace
95
{
96
LanguageTag g_defaultLanguageTag(u"en-US"_ustr, true);
97
LanguageTag g_loadLanguageTag(u"en-US"_ustr, true); //< The language used to load.
98
bool g_isDefaultTimezoneSet = false;
99
OUString g_DefaultTimezone;
100
const std::size_t g_logNotifierCacheMaxSize = 50;
101
::std::list<::std::string> g_logNotifierCache;
102
}
103
104
#if !defined NDEBUG
105
static bool isSfxMediumMissingInteractionHandled(SfxViewFrame& rViewFrame)
106
{
107
    const SfxObjectShell* pObjSh = rViewFrame.GetObjectShell();
108
    const SfxMedium* pMed = pObjSh ? pObjSh->GetMedium() : nullptr;
109
    if (!pMed)
110
    {
111
        // In this unlikely case it's not going to matter.
112
        return false;
113
    }
114
    const SfxUnoAnyItem *pItem = pMed->GetItemSet().GetItemIfSet(SID_INTERACTIONHANDLER, false);
115
    return !pItem || !pItem->GetValue().hasValue();
116
}
117
#endif
118
119
int SfxLokHelper::createView(SfxViewFrame& rViewFrame, ViewShellDocId docId)
120
0
{
121
0
    assert(docId >= ViewShellDocId(0) && "Cannot createView for invalid (negative) DocId.");
122
123
    // The XInteractionHandler installed during the original lo_documentLoad
124
    // should still be present when new views are created. If it is not
125
    // something has likely cleared the original SID_INTERACTIONHANDLER
126
0
    assert(!isSfxMediumMissingInteractionHandled(rViewFrame) && "original XInteractionHandler missing");
127
128
0
    comphelper::LibreOfficeKit::setDocId(docId);
129
0
    SfxRequest aRequest(rViewFrame, SID_NEWWINDOW);
130
0
    rViewFrame.ExecView_Impl(aRequest);
131
0
    SfxViewShell* pViewShell = SfxViewShell::Current();
132
0
    if (pViewShell == nullptr)
133
0
        return -1;
134
135
0
    assert(pViewShell->GetDocId() == docId && "DocId must be already set!");
136
0
    return static_cast<sal_Int32>(pViewShell->GetViewShellId());
137
0
}
138
139
int SfxLokHelper::createView()
140
0
{
141
    // Assumes a single document, or at least that the
142
    // current view belongs to the document on which the
143
    // view will be created.
144
0
    SfxViewShell* pViewShell = SfxViewShell::Current();
145
0
    if (pViewShell == nullptr)
146
0
        return -1;
147
148
0
    return createView(pViewShell->GetViewFrame(), pViewShell->GetDocId());
149
0
}
150
151
std::unordered_map<OUString, css::uno::Reference<css::ui::XAcceleratorConfiguration>>& SfxLokHelper::getAcceleratorConfs()
152
0
{
153
0
    return SfxApplication::GetOrCreate()->GetAcceleratorConfs_Impl();
154
0
}
155
156
int SfxLokHelper::createView(int nDocId)
157
0
{
158
0
    const SfxApplication* pApp = SfxApplication::Get();
159
0
    if (pApp == nullptr)
160
0
        return -1;
161
162
    // Find a shell with the given DocId.
163
0
    const ViewShellDocId docId(nDocId);
164
0
    for (const SfxViewShell* pViewShell : pApp->GetViewShells_Impl())
165
0
    {
166
0
        if (pViewShell->GetDocId() == docId)
167
0
            return createView(pViewShell->GetViewFrame(), docId);
168
0
    }
169
170
    // No frame with nDocId found.
171
0
    return -1;
172
0
}
173
174
void SfxLokHelper::setEditMode(int nMode, vcl::ITiledRenderable* pDoc)
175
0
{
176
0
    DisableCallbacks dc;
177
0
    pDoc->setEditMode(nMode);
178
0
}
179
180
void SfxLokHelper::destroyView(int nId)
181
0
{
182
0
    if (SfxViewShell* pViewShell = getViewOfId(nId))
183
0
    {
184
0
        pViewShell->SetLOKAccessibilityState(false);
185
0
        SfxViewFrame& rViewFrame = pViewShell->GetViewFrame();
186
0
        SfxRequest aRequest(rViewFrame, SID_CLOSEWIN);
187
0
        rViewFrame.Exec_Impl(aRequest);
188
0
    }
189
0
}
190
191
bool SfxLokHelper::isSettingView()
192
0
{
193
0
    return g_bSettingView;
194
0
}
195
196
void SfxLokHelper::setView(int nId)
197
0
{
198
0
    g_bSettingView = true;
199
0
    comphelper::ScopeGuard g([] { g_bSettingView = false; });
200
201
0
    const SfxViewShell* pViewShell = getViewOfId(nId);
202
0
    if (!pViewShell)
203
0
        return;
204
205
0
    DisableCallbacks dc;
206
207
0
    bool bIsCurrShell = (pViewShell == SfxViewShell::Current());
208
0
    if (bIsCurrShell && comphelper::LibreOfficeKit::getLanguageTag().getBcp47() == pViewShell->GetLOKLanguageTag().getBcp47())
209
0
        return;
210
211
0
    if (bIsCurrShell)
212
0
    {
213
        // If we wanted to set the SfxViewShell that is actually set, we could skip it.
214
        // But it looks like that the language can go wrong, so we have to fix that.
215
        // This can happen, when someone sets the language or SfxViewShell::Current() separately.
216
0
        SAL_WARN("lok", "LANGUAGE mismatch at setView! ... old (wrong) lang:"
217
0
                        << comphelper::LibreOfficeKit::getLanguageTag().getBcp47()
218
0
                        << " new lang:" << pViewShell->GetLOKLanguageTag().getBcp47());
219
0
    }
220
221
    // update the current LOK language and locale for the dialog tunneling
222
0
    comphelper::LibreOfficeKit::setLanguageTag(pViewShell->GetLOKLanguageTag());
223
0
    comphelper::LibreOfficeKit::setLocale(pViewShell->GetLOKLocale());
224
225
0
    if (bIsCurrShell)
226
0
        return;
227
228
0
    SfxViewFrame& rViewFrame = pViewShell->GetViewFrame();
229
0
    rViewFrame.MakeActive_Impl(false);
230
231
    // Make comphelper::dispatchCommand() find the correct frame.
232
0
    uno::Reference<frame::XFrame> xFrame = rViewFrame.GetFrame().GetFrameInterface();
233
0
    uno::Reference<frame::XDesktop2> xDesktop = frame::Desktop::create(comphelper::getProcessComponentContext());
234
0
    xDesktop->setActiveFrame(xFrame);
235
0
}
236
237
SfxViewShell* SfxLokHelper::getViewOfId(int nId)
238
0
{
239
0
    SfxApplication* pApp = SfxApplication::Get();
240
0
    if (pApp == nullptr)
241
0
        return nullptr;
242
243
0
    const ViewShellId nViewShellId(nId);
244
0
    std::vector<SfxViewShell*>& rViewArr = pApp->GetViewShells_Impl();
245
0
    for (SfxViewShell* pViewShell : rViewArr)
246
0
    {
247
0
        if (pViewShell->GetViewShellId() == nViewShellId)
248
0
            return pViewShell;
249
0
    }
250
251
0
    return nullptr;
252
0
}
253
254
int SfxLokHelper::getView(const SfxViewShell& rViewShell)
255
0
{
256
0
    return static_cast<sal_Int32>(rViewShell.GetViewShellId());
257
0
}
258
259
int SfxLokHelper::getCurrentView()
260
0
{
261
0
    SfxViewShell* pViewShell = SfxViewShell::Current();
262
    // No valid view shell? Then no idea.
263
0
    if (!pViewShell)
264
0
        return -1;
265
0
    return SfxLokHelper::getView(*pViewShell);
266
0
}
267
268
std::size_t SfxLokHelper::getViewsCount(int nDocId)
269
0
{
270
0
    assert(nDocId != -1 && "Cannot getViewsCount for invalid DocId -1");
271
272
0
    SfxApplication* pApp = SfxApplication::Get();
273
0
    if (!pApp)
274
0
        return 0;
275
276
0
    const ViewShellDocId nCurrentDocId(nDocId);
277
0
    std::size_t n = 0;
278
0
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
279
0
    while (pViewShell)
280
0
    {
281
0
        if (pViewShell->GetDocId() == nCurrentDocId)
282
0
            n++;
283
0
        pViewShell = SfxViewShell::GetNext(*pViewShell);
284
0
    }
285
286
0
    return n;
287
0
}
288
289
// SfxApplication::SetViewFrame_Impl ensures that ViewShells
290
// are kept in MRU order
291
int SfxLokHelper::getViewId(int nDocId)
292
0
{
293
0
    assert(nDocId != -1 && "Cannot getViewId for invalid DocId -1");
294
295
0
    SfxApplication* pApp = SfxApplication::Get();
296
0
    if (!pApp)
297
0
        return -1;
298
299
0
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
300
0
    while (pViewShell)
301
0
    {
302
0
        if (pViewShell->GetDocId().get() == nDocId)
303
0
            return pViewShell->GetViewShellId().get();
304
0
        pViewShell = SfxViewShell::GetNext(*pViewShell);
305
0
    }
306
307
0
    return -1;
308
0
}
309
310
std::size_t SfxLokHelper::getDocsCount()
311
0
{
312
0
    SfxApplication* pApp = SfxApplication::Get();
313
0
    if (!pApp)
314
0
        return 0;
315
316
0
    std::set<ViewShellDocId> aDocs;
317
318
0
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
319
0
    while (pViewShell)
320
0
    {
321
0
        aDocs.insert(pViewShell->GetDocId());
322
0
        pViewShell = SfxViewShell::GetNext(*pViewShell);
323
0
    }
324
325
0
    return aDocs.size();
326
0
}
327
328
bool SfxLokHelper::getViewIds(int nDocId, int* pArray, size_t nSize)
329
0
{
330
0
    assert(nDocId != -1 && "Cannot getViewsIds for invalid DocId -1");
331
332
0
    SfxApplication* pApp = SfxApplication::Get();
333
0
    if (!pApp)
334
0
        return false;
335
336
0
    const ViewShellDocId nCurrentDocId(nDocId);
337
0
    std::size_t n = 0;
338
0
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
339
0
    while (pViewShell)
340
0
    {
341
0
        if (pViewShell->GetDocId() == nCurrentDocId)
342
0
        {
343
0
            if (n == nSize)
344
0
                return false;
345
346
0
            pArray[n] = static_cast<sal_Int32>(pViewShell->GetViewShellId());
347
0
            n++;
348
0
        }
349
350
0
        pViewShell = SfxViewShell::GetNext(*pViewShell);
351
0
    }
352
353
0
    return true;
354
0
}
355
356
int SfxLokHelper::getDocumentIdOfView(int nViewId)
357
0
{
358
0
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
359
0
    while (pViewShell)
360
0
    {
361
0
        if (pViewShell->GetViewShellId() == ViewShellId(nViewId))
362
0
            return static_cast<int>(pViewShell->GetDocId());
363
0
        pViewShell = SfxViewShell::GetNext(*pViewShell);
364
0
    }
365
0
    return -1;
366
0
}
367
368
const LanguageTag & SfxLokHelper::getDefaultLanguage()
369
0
{
370
0
    return g_defaultLanguageTag;
371
0
}
372
373
void SfxLokHelper::setDefaultLanguage(const OUString& rBcp47LanguageTag)
374
0
{
375
0
    g_defaultLanguageTag = LanguageTag(rBcp47LanguageTag, true);
376
0
}
377
378
0
const LanguageTag& SfxLokHelper::getLoadLanguage() { return g_loadLanguageTag; }
379
380
void SfxLokHelper::setLoadLanguage(const OUString& rBcp47LanguageTag)
381
0
{
382
0
    g_loadLanguageTag = LanguageTag(rBcp47LanguageTag, true);
383
0
}
384
385
void SfxLokHelper::setViewLanguage(int nId, const OUString& rBcp47LanguageTag)
386
0
{
387
0
    if (SfxViewShell* pViewShell = getViewOfId(nId))
388
0
    {
389
0
        pViewShell->SetLOKLanguageTag(rBcp47LanguageTag);
390
        // sync also global getter if we are the current view
391
0
        bool bIsCurrShell = (pViewShell == SfxViewShell::Current());
392
0
        if (bIsCurrShell)
393
0
            comphelper::LibreOfficeKit::setLanguageTag(LanguageTag(rBcp47LanguageTag));
394
0
    }
395
0
}
396
397
void SfxLokHelper::setViewReadOnly(int nId, bool readOnly)
398
0
{
399
0
    if (SfxViewShell* pViewShell = getViewOfId(nId))
400
0
    {
401
0
        LOK_INFO("lok.readonlyview", "SfxLokHelper::setViewReadOnly: view id: " << nId << ", readOnly: " << readOnly);
402
0
        pViewShell->SetLokReadOnlyView(readOnly);
403
0
    }
404
0
}
405
406
void SfxLokHelper::setAccessibilityState(int nId, bool nEnabled)
407
0
{
408
0
    if (SfxViewShell* pViewShell = getViewOfId(nId))
409
0
    {
410
0
        LOK_INFO("lok.a11y", "SfxLokHelper::setAccessibilityState: view id: " << nId << ", nEnabled: " << nEnabled);
411
0
        pViewShell->SetLOKAccessibilityState(nEnabled);
412
0
    }
413
0
}
414
415
void SfxLokHelper::setViewLocale(int nId, const OUString& rBcp47LanguageTag)
416
0
{
417
0
    if (SfxViewShell* pViewShell = getViewOfId(nId))
418
0
    {
419
0
        pViewShell->SetLOKLocale(rBcp47LanguageTag);
420
0
        if (pViewShell->GetViewShellId() == ViewShellId(nId))
421
0
        {
422
            // sync also global getter if we are the current view
423
0
            bool bIsCurrShell = (pViewShell == SfxViewShell::Current());
424
0
            if (bIsCurrShell)
425
0
            {
426
0
                comphelper::LibreOfficeKit::setLocale(LanguageTag(rBcp47LanguageTag));
427
0
            }
428
0
            return;
429
0
        }
430
0
    }
431
0
}
432
433
void SfxLokHelper::setColorPreviewState(int nId, bool nEnabled)
434
0
{
435
0
    std::vector<SfxViewShell*>& rViewArr = SfxGetpApp()->GetViewShells_Impl();
436
437
0
    for (SfxViewShell* pViewShell : rViewArr)
438
0
    {
439
0
        if (pViewShell && pViewShell->GetViewShellId() == ViewShellId(nId))
440
0
        {
441
0
            pViewShell->SetLOKColorPreviewState(nEnabled);
442
0
            return;
443
0
        }
444
0
    }
445
0
}
446
447
void SfxLokHelper::setDefaultTimezone(bool isSet, const OUString& rTimezone)
448
0
{
449
0
    g_isDefaultTimezoneSet = isSet;
450
0
    g_DefaultTimezone = rTimezone;
451
0
}
452
453
std::pair<bool, OUString> SfxLokHelper::getDefaultTimezone()
454
0
{
455
0
    return { g_isDefaultTimezoneSet, g_DefaultTimezone };
456
0
}
457
458
void SfxLokHelper::setViewTimezone(int nId, bool isSet, const OUString& rTimezone)
459
0
{
460
0
    if (SfxViewShell* pViewShell = getViewOfId(nId))
461
0
    {
462
0
        pViewShell->SetLOKTimezone(isSet, rTimezone);
463
0
    }
464
0
}
465
466
std::pair<bool, OUString> SfxLokHelper::getViewTimezone(int nId)
467
0
{
468
0
    if (SfxViewShell* pViewShell = getViewOfId(nId))
469
0
    {
470
0
        return pViewShell->GetLOKTimezone();
471
0
    }
472
473
0
    return {};
474
0
}
475
476
/*
477
* Used for putting a whole JSON string into a string value
478
* e.g { key: "{JSON}" }
479
*/
480
static OString lcl_sanitizeJSONAsValue(const OString &rStr)
481
0
{
482
0
    if (rStr.getLength() < 1)
483
0
        return rStr;
484
    // FIXME: need an optimized 'escape' method for O[U]String.
485
0
    OStringBuffer aBuf(rStr.getLength() + 8);
486
0
    for (sal_Int32 i = 0; i < rStr.getLength(); ++i)
487
0
    {
488
0
        if (rStr[i] == '"' || rStr[i] == '\\')
489
0
            aBuf.append('\\');
490
491
0
        if (rStr[i] != '\n')
492
0
            aBuf.append(rStr[i]);
493
0
    }
494
0
    return aBuf.makeStringAndClear();
495
0
}
496
497
static OString lcl_generateJSON(const SfxViewShell& rView, const boost::property_tree::ptree& rTree)
498
0
{
499
0
    boost::property_tree::ptree aMessageProps = rTree;
500
0
    aMessageProps.put("viewId", SfxLokHelper::getView(rView));
501
0
    aMessageProps.put("part", rView.getPart());
502
0
    aMessageProps.put("mode", rView.getEditMode());
503
0
    std::stringstream aStream;
504
0
    boost::property_tree::write_json(aStream, aMessageProps, false /* pretty */);
505
0
    return OString(o3tl::trim(aStream.str()));
506
0
}
507
508
static inline OString lcl_generateJSON(const SfxViewShell& rView, int nViewId, std::string_view rKey,
509
                                       const OString& rPayload)
510
0
{
511
0
    return OString::Concat("{ \"viewId\": \"") + OString::number(nViewId)
512
0
           + "\", \"part\": \"" + OString::number(rView.getPart()) + "\", \"mode\": \""
513
0
           + OString::number(rView.getEditMode()) + "\", \"" + rKey + "\": \""
514
0
           + lcl_sanitizeJSONAsValue(rPayload) + "\" }";
515
0
}
516
517
static inline OString lcl_generateJSON(const SfxViewShell& rView, std::string_view rKey,
518
                                       const OString& rPayload)
519
0
{
520
0
    return lcl_generateJSON(rView, SfxLokHelper::getView(rView), rKey, rPayload);
521
0
}
522
523
void SfxLokHelper::notifyOtherView(const SfxViewShell& rThisView, SfxViewShell const* pOtherView,
524
                                   int nType, std::string_view rKey, const OString& rPayload)
525
0
{
526
0
    if (DisableCallbacks::disabled())
527
0
        return;
528
529
0
    const OString aPayload = lcl_generateJSON(rThisView, rKey, rPayload);
530
0
    const int viewId = SfxLokHelper::getView(rThisView);
531
0
    pOtherView->libreOfficeKitViewCallbackWithViewId(nType, aPayload, viewId);
532
0
}
533
534
void SfxLokHelper::notifyOtherView(const SfxViewShell& rThisView, SfxViewShell const* pOtherView,
535
                                   int nType, const boost::property_tree::ptree& rTree)
536
0
{
537
0
    if (DisableCallbacks::disabled() || !pOtherView)
538
0
        return;
539
540
0
    const int viewId = SfxLokHelper::getView(rThisView);
541
0
    pOtherView->libreOfficeKitViewCallbackWithViewId(nType, lcl_generateJSON(rThisView, rTree), viewId);
542
0
}
543
544
void SfxLokHelper::notifyOtherViews(const SfxViewShell* pThisView, int nType, std::string_view rKey,
545
                                    const OString& rPayload)
546
18.6k
{
547
18.6k
    assert(pThisView != nullptr && "pThisView must be valid");
548
18.6k
    if (DisableCallbacks::disabled())
549
18.6k
        return;
550
551
    // Cache the payload so we only have to generate it once, at most.
552
0
    OString aPayload;
553
0
    int viewId = -1;
554
555
0
    const ViewShellDocId nCurrentDocId = pThisView->GetDocId();
556
0
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
557
0
    while (pViewShell)
558
0
    {
559
0
        if (pViewShell != pThisView && nCurrentDocId == pViewShell->GetDocId())
560
0
        {
561
            // Payload is only dependent on pThisView.
562
0
            if (aPayload.isEmpty())
563
0
            {
564
0
                aPayload = lcl_generateJSON(*pThisView, rKey, rPayload);
565
0
                viewId = SfxLokHelper::getView(*pThisView);
566
0
            }
567
568
0
            pViewShell->libreOfficeKitViewCallbackWithViewId(nType, aPayload, viewId);
569
0
        }
570
571
0
        pViewShell = SfxViewShell::GetNext(*pViewShell);
572
0
    }
573
0
}
574
575
void SfxLokHelper::notifyOtherViews(const SfxViewShell* pThisView, int nType,
576
                                    const boost::property_tree::ptree& rTree)
577
0
{
578
0
    assert(pThisView != nullptr && "pThisView must be valid");
579
0
    if (!pThisView || DisableCallbacks::disabled())
580
0
        return;
581
582
    // Cache the payload so we only have to generate it once, at most.
583
0
    OString aPayload;
584
0
    int viewId = -1;
585
586
0
    const ViewShellDocId nCurrentDocId = pThisView->GetDocId();
587
0
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
588
0
    while (pViewShell)
589
0
    {
590
0
        if (pViewShell != pThisView && nCurrentDocId == pViewShell->GetDocId())
591
0
        {
592
            // Payload is only dependent on pThisView.
593
0
            if (aPayload.isEmpty())
594
0
            {
595
0
                aPayload = lcl_generateJSON(*pThisView, rTree);
596
0
                viewId = SfxLokHelper::getView(*pThisView);
597
0
            }
598
599
0
            pViewShell->libreOfficeKitViewCallbackWithViewId(nType, aPayload, viewId);
600
0
        }
601
602
0
        pViewShell = SfxViewShell::GetNext(*pViewShell);
603
0
    }
604
0
}
605
606
OString SfxLokHelper::makePayloadJSON(const SfxViewShell* pThisView, int nViewId, std::string_view rKey, const OString& rPayload)
607
0
{
608
0
    return lcl_generateJSON(*pThisView, nViewId, rKey, rPayload);
609
0
}
610
611
namespace {
612
    OUString lcl_getNameForSlot(const SfxViewShell* pShell, sal_uInt16 nWhich)
613
0
    {
614
0
        if (pShell && pShell->GetFrame())
615
0
        {
616
0
            const SfxSlot* pSlot = SfxSlotPool::GetSlotPool(pShell->GetFrame()).GetSlot(nWhich);
617
0
            if (pSlot)
618
0
            {
619
0
                if (!pSlot->GetUnoName().isEmpty())
620
0
                {
621
0
                    return pSlot->GetCommand();
622
0
                }
623
0
            }
624
0
        }
625
626
0
        return u""_ustr;
627
0
    }
628
}
629
630
void SfxLokHelper::sendUnoStatus(const SfxViewShell* pShell, const SfxPoolItem* pItem)
631
0
{
632
0
    if (!pShell || !pItem || IsInvalidItem(pItem) || DisableCallbacks::disabled())
633
0
        return;
634
635
0
    boost::property_tree::ptree aItem = pItem->dumpAsJSON();
636
637
0
    if (aItem.count("state"))
638
0
    {
639
0
        OUString sCommand = lcl_getNameForSlot(pShell, pItem->Which());
640
0
        if (!sCommand.isEmpty())
641
0
            aItem.put("commandName", sCommand);
642
643
0
        std::stringstream aStream;
644
0
        boost::property_tree::write_json(aStream, aItem);
645
0
        pShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED, OString(aStream.str()));
646
0
    }
647
0
}
648
649
void SfxLokHelper::notifyViewRenderState(const SfxViewShell* pShell, vcl::ITiledRenderable* pDoc)
650
12.8k
{
651
12.8k
    pShell->libreOfficeKitViewCallback(LOK_CALLBACK_VIEW_RENDER_STATE, pDoc->getViewRenderState());
652
12.8k
}
653
654
void SfxLokHelper::notifyWindow(const SfxViewShell* pThisView,
655
                                vcl::LOKWindowId nLOKWindowId,
656
                                std::u16string_view rAction,
657
                                const std::vector<vcl::LOKPayloadItem>& rPayload)
658
0
{
659
0
    assert(pThisView != nullptr && "pThisView must be valid");
660
661
0
    if (nLOKWindowId == 0 || DisableCallbacks::disabled())
662
0
        return;
663
664
0
    OStringBuffer aPayload =
665
0
        "{ \"id\": \"" + OString::number(nLOKWindowId) + "\""
666
0
        ", \"action\": \"" + OUStringToOString(rAction, RTL_TEXTENCODING_UTF8) + "\"";
667
668
0
    for (const auto& rItem: rPayload)
669
0
    {
670
0
        if (!rItem.first.isEmpty() && !rItem.second.isEmpty())
671
0
        {
672
0
            auto aFirst = rItem.first.replaceAll("\""_ostr, "\\\""_ostr);
673
0
            auto aSecond = rItem.second.replaceAll("\""_ostr, "\\\""_ostr);
674
0
            aPayload.append(", \"" + aFirst + "\": \"" + aSecond + "\"");
675
0
        }
676
0
    }
677
0
    aPayload.append('}');
678
679
0
    const OString s = aPayload.makeStringAndClear();
680
0
    pThisView->libreOfficeKitViewCallback(LOK_CALLBACK_WINDOW, s);
681
0
}
682
683
void SfxLokHelper::notifyCursorInvalidation(SfxViewShell const* pThisView, tools::Rectangle const* pRect, bool bControlEvent, int windowID)
684
0
{
685
0
    int nViewId = SfxLokHelper::getView(*pThisView);
686
0
    OString sPayload = OString::Concat("{ \"viewId\": \"") + OString::number(nViewId) + "\", \"rectangle\": \"" + pRect->toString();
687
0
    if (bControlEvent)
688
0
    {
689
0
        sPayload += "\", \"controlEvent\": true, \"windowId\": \"" + OString::number(windowID) + "\"";
690
0
    }
691
0
    sPayload += " }";
692
0
    pThisView->libreOfficeKitViewCallback(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, sPayload);
693
0
}
694
695
void SfxLokHelper::notifyInvalidation(SfxViewShell const* pThisView, tools::Rectangle const* pRect)
696
0
{
697
    // -1 means all parts
698
0
    const int nPart = comphelper::LibreOfficeKit::isPartInInvalidation() ? pThisView->getPart() : INT_MIN;
699
0
    SfxLokHelper::notifyInvalidation(pThisView, nPart, pRect);
700
0
}
701
702
void SfxLokHelper::notifyInvalidation(SfxViewShell const* pThisView, const int nInPart, tools::Rectangle const* pRect)
703
0
{
704
0
    if (DisableCallbacks::disabled())
705
0
        return;
706
707
    // -1 means all parts
708
0
    const int nPart = comphelper::LibreOfficeKit::isPartInInvalidation() ? nInPart : INT_MIN;
709
0
    const int nMode = pThisView->getEditMode();
710
0
    pThisView->libreOfficeKitViewInvalidateTilesCallback(pRect, nPart, nMode);
711
0
}
712
713
void SfxLokHelper::notifyDocumentSizeChanged(SfxViewShell const* pThisView, const OString& rPayload, vcl::ITiledRenderable* pDoc, bool bInvalidateAll)
714
0
{
715
0
    if (!pDoc || pDoc->isDisposed() || DisableCallbacks::disabled())
716
0
        return;
717
718
0
    if (bInvalidateAll)
719
0
    {
720
0
        for (int i = 0; i < pDoc->getParts(); ++i)
721
0
        {
722
0
            tools::Rectangle aRectangle(0, 0, 1000000000, 1000000000);
723
0
            const int nMode = pThisView->getEditMode();
724
0
            pThisView->libreOfficeKitViewInvalidateTilesCallback(&aRectangle, i, nMode);
725
0
        }
726
0
    }
727
0
    pThisView->libreOfficeKitViewCallback(LOK_CALLBACK_DOCUMENT_SIZE_CHANGED, rPayload);
728
0
}
729
730
void SfxLokHelper::notifyDocumentSizeChangedAllViews(vcl::ITiledRenderable* pDoc, bool bInvalidateAll)
731
0
{
732
0
    if (DisableCallbacks::disabled())
733
0
        return;
734
735
    // FIXME: Do we know whether it is the views for the document that is in the "current" view that has changed?
736
0
    const SfxViewShell* const pCurrentViewShell = SfxViewShell::Current();
737
0
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
738
0
    while (pViewShell)
739
0
    {
740
        // FIXME: What if SfxViewShell::Current() returned null?
741
        // Should we then do this for all views of all open documents
742
        // or not?
743
0
        if (pCurrentViewShell == nullptr || pViewShell->GetDocId() == pCurrentViewShell-> GetDocId())
744
0
        {
745
0
            SfxLokHelper::notifyDocumentSizeChanged(pViewShell, ""_ostr, pDoc, bInvalidateAll);
746
0
            bInvalidateAll = false; // we direct invalidations to all views anyway.
747
0
        }
748
0
        pViewShell = SfxViewShell::GetNext(*pViewShell);
749
0
    }
750
0
}
751
752
void SfxLokHelper::notifyCurrentPageSizeChangedAllViews(const vcl::ITiledRenderable *pDoc)
753
0
{
754
0
    if (!pDoc || pDoc->isDisposed() || DisableCallbacks::disabled())
755
0
        return;
756
757
0
    const SfxViewShell* const pCurrentViewShell = SfxViewShell::Current();
758
0
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
759
0
    while (pViewShell)
760
0
    {
761
0
        if (pCurrentViewShell == nullptr || pViewShell->GetDocId() == pCurrentViewShell->GetDocId())
762
0
        {
763
0
            OString aPayload = ".uno:CurrentPageResize"_ostr;
764
0
            pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED, aPayload);
765
0
        }
766
0
        pViewShell = SfxViewShell::GetNext(*pViewShell);
767
0
    }
768
0
}
769
770
void SfxLokHelper::notifyPartSizeChangedAllViews(vcl::ITiledRenderable* pDoc, int nPart)
771
0
{
772
0
    if (DisableCallbacks::disabled())
773
0
        return;
774
775
0
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
776
0
    while (pViewShell)
777
0
    {
778
0
        if (// FIXME should really filter on pViewShell->GetDocId() too
779
0
            pViewShell->getPart() == nPart)
780
0
            SfxLokHelper::notifyDocumentSizeChanged(pViewShell, ""_ostr, pDoc, false);
781
0
        pViewShell = SfxViewShell::GetNext(*pViewShell);
782
0
    }
783
0
}
784
785
OString SfxLokHelper::makeVisCursorInvalidation(int nViewId, const OString& rRectangle,
786
    bool bMispelledWord, const OString& rHyperlink)
787
0
{
788
0
    if (comphelper::LibreOfficeKit::isViewIdForVisCursorInvalidation())
789
0
    {
790
0
        OString sHyperlink = rHyperlink.isEmpty() ? "{}"_ostr : rHyperlink;
791
0
        return OString::Concat("{ \"viewId\": \"") + OString::number(nViewId) +
792
0
            "\", \"rectangle\": \"" + rRectangle +
793
0
            "\", \"mispelledWord\": \"" +  OString::number(bMispelledWord ? 1 : 0) +
794
0
            "\", \"hyperlink\": " + sHyperlink + " }";
795
0
    }
796
0
    else
797
0
    {
798
0
        return rRectangle;
799
0
    }
800
0
}
801
802
void SfxLokHelper::notifyAllViews(int nType, const OString& rPayload)
803
0
{
804
0
    if (DisableCallbacks::disabled())
805
0
        return;
806
807
0
    const auto payload = rPayload.getStr();
808
0
    const SfxViewShell* const pCurrentViewShell = SfxViewShell::Current();
809
0
    if (!pCurrentViewShell)
810
0
        return;
811
0
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
812
0
    while (pViewShell)
813
0
    {
814
0
        if (pViewShell->GetDocId() == pCurrentViewShell->GetDocId())
815
0
            pViewShell->libreOfficeKitViewCallback(nType, payload);
816
0
        pViewShell = SfxViewShell::GetNext(*pViewShell);
817
0
    }
818
0
}
819
820
void SfxLokHelper::notifyContextChange(const css::ui::ContextChangeEventObject& rEvent)
821
0
{
822
0
    if (DisableCallbacks::disabled())
823
0
        return;
824
825
0
    SfxViewShell* pViewShell = SfxViewShell::Get({ rEvent.Source, css::uno::UNO_QUERY });
826
0
    if (!pViewShell)
827
0
        return;
828
829
0
    OUString aBuffer =
830
0
        rEvent.ApplicationName.replace(' ', '_') +
831
0
        " " +
832
0
        rEvent.ContextName.replace(' ', '_');
833
0
    pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CONTEXT_CHANGED, aBuffer.toUtf8());
834
0
}
835
836
void SfxLokHelper::notifyLog(const std::ostringstream& stream)
837
0
{
838
0
    if (DisableCallbacks::disabled())
839
0
       return;
840
841
0
    SfxViewShell* pViewShell = SfxViewShell::Current();
842
0
    if (!pViewShell)
843
0
       return;
844
0
    if (pViewShell->getLibreOfficeKitViewCallback())
845
0
    {
846
0
        if (!g_logNotifierCache.empty())
847
0
        {
848
0
            for (const auto& msg : g_logNotifierCache)
849
0
            {
850
0
                pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CORE_LOG, msg.c_str());
851
0
            }
852
0
            g_logNotifierCache.clear();
853
0
        }
854
0
        pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CORE_LOG, stream.str().c_str());
855
0
    }
856
0
    else
857
0
    {
858
0
        while (g_logNotifierCache.size() >= g_logNotifierCacheMaxSize)
859
0
            g_logNotifierCache.pop_front();
860
0
        g_logNotifierCache.push_back(stream.str());
861
0
    }
862
0
}
863
864
namespace
865
{
866
std::string extractCertificateWithOffset(const std::string& certificate, size_t& rOffset)
867
0
{
868
0
    static constexpr std::string_view header("-----BEGIN CERTIFICATE-----");
869
0
    static constexpr std::string_view footer("-----END CERTIFICATE-----");
870
871
0
    std::string result;
872
873
0
    size_t pos1 = certificate.find(header, rOffset);
874
0
    if (pos1 == std::string::npos)
875
0
        return result;
876
877
0
    size_t pos2 = certificate.find(footer, pos1 + 1);
878
0
    if (pos2 == std::string::npos)
879
0
        return result;
880
881
0
    pos1 = pos1 + header.length();
882
0
    size_t len = pos2 - pos1;
883
884
0
    rOffset = pos2;
885
0
    return certificate.substr(pos1, len);
886
0
}
887
}
888
889
std::string SfxLokHelper::extractCertificate(const std::string & certificate)
890
0
{
891
0
    size_t nOffset = 0;
892
0
    return extractCertificateWithOffset(certificate, nOffset);
893
0
}
894
895
std::vector<std::string> SfxLokHelper::extractCertificates(const std::string& rCerts)
896
0
{
897
0
    std::vector<std::string> aRet;
898
0
    size_t nOffset = 0;
899
0
    while (true)
900
0
    {
901
0
        std::string aNext = extractCertificateWithOffset(rCerts, nOffset);
902
0
        if (aNext.empty())
903
0
        {
904
0
            break;
905
0
        }
906
907
0
        aRet.push_back(std::move(aNext));
908
0
    }
909
0
    return aRet;
910
0
}
911
912
namespace
913
{
914
std::string extractKey(const std::string & privateKey)
915
0
{
916
0
    static constexpr std::string_view header("-----BEGIN PRIVATE KEY-----");
917
0
    static constexpr std::string_view footer("-----END PRIVATE KEY-----");
918
919
0
    std::string result;
920
921
0
    size_t pos1 = privateKey.find(header);
922
0
    if (pos1 == std::string::npos)
923
0
        return result;
924
925
0
    size_t pos2 = privateKey.find(footer, pos1 + 1);
926
0
    if (pos2 == std::string::npos)
927
0
        return result;
928
929
0
    pos1 = pos1 + header.length();
930
0
    pos2 = pos2 - pos1;
931
932
0
    return privateKey.substr(pos1, pos2);
933
0
}
934
}
935
936
css::uno::Reference<css::security::XCertificate> SfxLokHelper::getSigningCertificate(const std::string& rCert, const std::string& rKey)
937
0
{
938
0
    const uno::Reference<uno::XComponentContext>& xContext = comphelper::getProcessComponentContext();
939
0
    uno::Reference<xml::crypto::XSEInitializer> xSEInitializer = xml::crypto::SEInitializer::create(xContext);
940
0
    uno::Reference<xml::crypto::XXMLSecurityContext> xSecurityContext = xSEInitializer->createSecurityContext(OUString());
941
0
    if (!xSecurityContext.is())
942
0
    {
943
0
        return {};
944
0
    }
945
946
0
    uno::Reference<xml::crypto::XSecurityEnvironment> xSecurityEnvironment = xSecurityContext->getSecurityEnvironment();
947
0
    uno::Reference<xml::crypto::XCertificateCreator> xCertificateCreator(xSecurityEnvironment, uno::UNO_QUERY);
948
949
0
    if (!xCertificateCreator.is())
950
0
    {
951
0
        return {};
952
0
    }
953
954
0
    uno::Sequence<sal_Int8> aCertificateSequence;
955
956
0
    std::string aCertificateBase64String = extractCertificate(rCert);
957
0
    if (!aCertificateBase64String.empty())
958
0
    {
959
0
        OUString aBase64OUString = OUString::createFromAscii(aCertificateBase64String);
960
0
        comphelper::Base64::decode(aCertificateSequence, aBase64OUString);
961
0
    }
962
0
    else
963
0
    {
964
0
        aCertificateSequence.realloc(rCert.size());
965
0
        std::copy(rCert.c_str(), rCert.c_str() + rCert.size(), aCertificateSequence.getArray());
966
0
    }
967
968
0
    uno::Sequence<sal_Int8> aPrivateKeySequence;
969
0
    std::string aPrivateKeyBase64String = extractKey(rKey);
970
0
    if (!aPrivateKeyBase64String.empty())
971
0
    {
972
0
        OUString aBase64OUString = OUString::createFromAscii(aPrivateKeyBase64String);
973
0
        comphelper::Base64::decode(aPrivateKeySequence, aBase64OUString);
974
0
    }
975
0
    else
976
0
    {
977
0
        aPrivateKeySequence.realloc(rKey.size());
978
0
        std::copy(rKey.c_str(), rKey.c_str() + rKey.size(), aPrivateKeySequence.getArray());
979
0
    }
980
981
0
    uno::Reference<security::XCertificate> xCertificate = xCertificateCreator->createDERCertificateWithPrivateKey(aCertificateSequence, aPrivateKeySequence);
982
0
    return xCertificate;
983
0
}
984
985
uno::Reference<security::XCertificate> SfxLokHelper::addCertificate(
986
    const css::uno::Reference<css::xml::crypto::XCertificateCreator>& xCertificateCreator,
987
    const css::uno::Sequence<sal_Int8>& rCert)
988
0
{
989
    // Trust arg is handled by CERT_DecodeTrustString(), see 'man certutil'.
990
0
    return xCertificateCreator->addDERCertificateToTheDatabase(rCert, u"TCu,Cu,Tu"_ustr);
991
0
}
992
993
void SfxLokHelper::addCertificates(const std::vector<std::string>& rCerts)
994
0
{
995
0
    const uno::Reference<uno::XComponentContext>& xContext = comphelper::getProcessComponentContext();
996
0
    uno::Reference<xml::crypto::XSEInitializer> xSEInitializer = xml::crypto::SEInitializer::create(xContext);
997
0
    uno::Reference<xml::crypto::XXMLSecurityContext> xSecurityContext = xSEInitializer->createSecurityContext(OUString());
998
0
    if (!xSecurityContext.is())
999
0
    {
1000
0
        return;
1001
0
    }
1002
1003
0
    uno::Reference<xml::crypto::XSecurityEnvironment> xSecurityEnvironment = xSecurityContext->getSecurityEnvironment();
1004
0
    uno::Reference<xml::crypto::XCertificateCreator> xCertificateCreator(xSecurityEnvironment, uno::UNO_QUERY);
1005
0
    if (!xCertificateCreator.is())
1006
0
    {
1007
0
        return;
1008
0
    }
1009
1010
0
    for (const auto& rCert : rCerts)
1011
0
    {
1012
0
        uno::Sequence<sal_Int8> aCertificateSequence;
1013
0
        OUString aBase64OUString = OUString::fromUtf8(rCert);
1014
0
        comphelper::Base64::decode(aCertificateSequence, aBase64OUString);
1015
0
        addCertificate(xCertificateCreator, aCertificateSequence);
1016
0
    }
1017
1018
    // Update the signature state, perhaps the signing certificate is now trusted.
1019
0
    SfxObjectShell* pObjectShell = SfxObjectShell::Current();
1020
0
    if (!pObjectShell)
1021
0
    {
1022
0
        return;
1023
0
    }
1024
1025
0
    pObjectShell->RecheckSignature(false);
1026
0
}
1027
1028
bool SfxLokHelper::supportsCommand(std::u16string_view rCommand)
1029
0
{
1030
0
    static const std::initializer_list<std::u16string_view> vSupport = { u"Signature" };
1031
1032
0
    return std::find(vSupport.begin(), vSupport.end(), rCommand) != vSupport.end();
1033
0
}
1034
1035
std::map<OUString, OUString> SfxLokHelper::parseCommandParameters(std::u16string_view rCommand)
1036
0
{
1037
0
    std::map<OUString, OUString> aMap;
1038
1039
0
    INetURLObject aParser(rCommand);
1040
0
    OUString aArguments = aParser.GetParam();
1041
0
    sal_Int32 nParamIndex = 0;
1042
0
    do
1043
0
    {
1044
0
        std::u16string_view aParam = o3tl::getToken(aArguments, 0, '&', nParamIndex);
1045
0
        sal_Int32 nIndex = 0;
1046
0
        OUString aKey;
1047
0
        OUString aValue;
1048
0
        do
1049
0
        {
1050
0
            std::u16string_view aToken = o3tl::getToken(aParam, 0, '=', nIndex);
1051
0
            if (aKey.isEmpty())
1052
0
                aKey = aToken;
1053
0
            else
1054
0
                aValue = aToken;
1055
0
        } while (nIndex >= 0);
1056
0
        aMap[aKey] = INetURLObject::decode(aValue, INetURLObject::DecodeMechanism::WithCharset);
1057
0
    } while (nParamIndex >= 0);
1058
1059
0
    return aMap;
1060
0
}
1061
1062
void SfxLokHelper::getCommandValues(tools::JsonWriter& rJsonWriter, std::string_view rCommand)
1063
0
{
1064
0
    static constexpr OString aSignature(".uno:Signature"_ostr);
1065
0
    if (!o3tl::starts_with(rCommand, aSignature))
1066
0
    {
1067
0
        return;
1068
0
    }
1069
1070
0
    SfxObjectShell* pObjectShell = SfxObjectShell::Current();
1071
0
    if (!pObjectShell)
1072
0
    {
1073
0
        return;
1074
0
    }
1075
1076
0
    svl::crypto::SigningContext aSigningContext;
1077
0
    std::map<OUString, OUString> aMap
1078
0
        = SfxLokHelper::parseCommandParameters(OUString::fromUtf8(rCommand));
1079
0
    auto it = aMap.find("signatureTime");
1080
0
    if (it != aMap.end())
1081
0
    {
1082
        // Signature time is provided: prefer it over the system time.
1083
0
        aSigningContext.m_nSignatureTime = it->second.toInt64();
1084
0
    }
1085
0
    pObjectShell->SignDocumentContentUsingCertificate(aSigningContext);
1086
    // Set commandName, this is a reply to a request.
1087
0
    rJsonWriter.put("commandName", aSignature);
1088
0
    auto aCommandValues = rJsonWriter.startNode("commandValues");
1089
0
    rJsonWriter.put("signatureTime", aSigningContext.m_nSignatureTime);
1090
1091
0
    uno::Sequence<sal_Int8> aDigest(reinterpret_cast<sal_Int8*>(aSigningContext.m_aDigest.data()),
1092
0
                                    aSigningContext.m_aDigest.size());
1093
0
    OUStringBuffer aBuffer;
1094
0
    comphelper::Base64::encode(aBuffer, aDigest);
1095
0
    rJsonWriter.put("digest", aBuffer.makeStringAndClear());
1096
0
}
1097
1098
void SfxLokHelper::notifyUpdate(SfxViewShell const* pThisView, int nType)
1099
0
{
1100
0
    if (DisableCallbacks::disabled() || !pThisView)
1101
0
        return;
1102
1103
0
    pThisView->libreOfficeKitViewUpdatedCallback(nType);
1104
0
}
1105
1106
void SfxLokHelper::notifyUpdatePerViewId(SfxViewShell const& rThisView, int nType)
1107
0
{
1108
0
    notifyUpdatePerViewId(rThisView, &rThisView, rThisView, nType);
1109
0
}
1110
1111
void SfxLokHelper::notifyUpdatePerViewId(SfxViewShell const& rTargetShell, SfxViewShell const* pViewShell,
1112
    SfxViewShell const& rSourceShell, int nType)
1113
0
{
1114
0
    if (DisableCallbacks::disabled())
1115
0
        return;
1116
1117
    // This getCurrentView() is dubious
1118
0
    SAL_WARN_IF(!pViewShell, "lok", "no explicit viewshell set");
1119
0
    int viewId = pViewShell ? SfxLokHelper::getView(*pViewShell) : SfxLokHelper::getCurrentView();
1120
0
    int sourceViewId = SfxLokHelper::getView(rSourceShell);
1121
0
    rTargetShell.libreOfficeKitViewUpdatedCallbackPerViewId(nType, viewId, sourceViewId);
1122
0
}
1123
1124
void SfxLokHelper::notifyOtherViewsUpdatePerViewId(SfxViewShell const* pThisView, int nType)
1125
0
{
1126
0
    if (DisableCallbacks::disabled() || !pThisView)
1127
0
        return;
1128
1129
0
    int viewId = SfxLokHelper::getView(*pThisView);
1130
0
    const ViewShellDocId nCurrentDocId = pThisView->GetDocId();
1131
0
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
1132
0
    while (pViewShell)
1133
0
    {
1134
0
        if (pViewShell != pThisView && nCurrentDocId == pViewShell->GetDocId())
1135
0
            pViewShell->libreOfficeKitViewUpdatedCallbackPerViewId(nType, viewId, viewId);
1136
1137
0
        pViewShell = SfxViewShell::GetNext(*pViewShell);
1138
0
    }
1139
0
}
1140
1141
void SfxLokHelper::registerViewCallbacks()
1142
0
{
1143
0
    comphelper::LibreOfficeKit::setViewSetter([](int nView) {
1144
0
        SfxLokHelper::setView(nView);
1145
0
    });
1146
0
    comphelper::LibreOfficeKit::setViewGetter([]() -> int {
1147
0
        return SfxLokHelper::getCurrentView();
1148
0
    });
1149
0
}
1150
1151
void SfxLokHelper::dispatchUnoCommand(const boost::property_tree::ptree& tree)
1152
0
{
1153
0
    auto command = OStringToOUString(tree.get_child("name").get_value<std::string>(),
1154
0
                                     RTL_TEXTENCODING_UTF8);
1155
    // Check if the uno command is allowed
1156
0
    if (std::u16string_view rest;
1157
0
        !command.startsWith(".uno:", &rest) || !GetKitUnoCommandList().contains(rest))
1158
0
    {
1159
0
        LOK_WARN("lok.transform",
1160
0
                 "UnoCommand command is not recognized: '" << command << "'");
1161
0
        return;
1162
0
    }
1163
0
    std::vector<beans::PropertyValue> arguments;
1164
0
    if (auto args = tree.get_child_optional("arguments"))
1165
0
    {
1166
0
        arguments = comphelper::JsonToPropertyValues(*args);
1167
0
    }
1168
    // Make the uno command synchron
1169
0
    arguments.push_back(comphelper::makePropertyValue(u"SynchronMode"_ustr, true));
1170
1171
0
    comphelper::dispatchCommand(command, comphelper::containerToSequence(arguments));
1172
0
}
1173
1174
namespace
1175
{
1176
    struct LOKAsyncEventData
1177
    {
1178
        int mnView; // Window is not enough.
1179
        VclPtr<vcl::Window> mpWindow;
1180
        VclEventId mnEvent;
1181
        MouseEvent maMouseEvent;
1182
        KeyEvent maKeyEvent;
1183
        OUString maText;
1184
    };
1185
1186
    void LOKPostAsyncEvent(void* pEv, void*)
1187
0
    {
1188
0
        std::unique_ptr<LOKAsyncEventData> pLOKEv(static_cast<LOKAsyncEventData*>(pEv));
1189
0
        if (pLOKEv->mpWindow->isDisposed())
1190
0
            return;
1191
1192
0
        int nView = SfxLokHelper::getCurrentView();
1193
0
        if (nView != pLOKEv->mnView)
1194
0
        {
1195
0
            SAL_INFO("sfx.view", "LOK - view mismatch " << nView << " vs. " << pLOKEv->mnView);
1196
0
            SfxLokHelper::setView(pLOKEv->mnView);
1197
0
        }
1198
1199
0
        if (!pLOKEv->mpWindow->HasChildPathFocus(true))
1200
0
        {
1201
0
            SAL_INFO("sfx.view", "LOK - focus mismatch, switching focus");
1202
0
            pLOKEv->mpWindow->GrabFocus();
1203
0
        }
1204
1205
0
        VclPtr<vcl::Window> pFocusWindow = pLOKEv->mpWindow->GetFocusedWindow();
1206
0
        if (!pFocusWindow)
1207
0
            pFocusWindow = pLOKEv->mpWindow;
1208
1209
0
        if (pLOKEv->mpWindow->isDisposed())
1210
0
            return;
1211
1212
0
        switch (pLOKEv->mnEvent)
1213
0
        {
1214
0
        case VclEventId::WindowKeyInput:
1215
0
        {
1216
0
            sal_uInt16 nRepeat = pLOKEv->maKeyEvent.GetRepeat();
1217
0
            KeyEvent singlePress(pLOKEv->maKeyEvent.GetCharCode(),
1218
0
                                 pLOKEv->maKeyEvent.GetKeyCode());
1219
0
            for (sal_uInt16 i = 0; i <= nRepeat; ++i)
1220
0
                if (!pFocusWindow->isDisposed())
1221
0
                    pFocusWindow->KeyInput(singlePress);
1222
1223
0
            if (pLOKEv->maKeyEvent.GetKeyCode().GetCode() == KEY_CONTEXTMENU)
1224
0
            {
1225
                // later do use getCaretPosition probably, or get focused obj position, smt like that
1226
0
                Point aPos = pFocusWindow->GetPointerPosPixel();
1227
0
                CommandEvent aCEvt( aPos, CommandEventId::ContextMenu);
1228
0
                pFocusWindow->Command(aCEvt);
1229
0
            }
1230
0
            break;
1231
0
        }
1232
0
        case VclEventId::WindowKeyUp:
1233
0
            if (!pFocusWindow->isDisposed())
1234
0
                pFocusWindow->KeyUp(pLOKEv->maKeyEvent);
1235
0
            break;
1236
0
        case VclEventId::WindowMouseButtonDown:
1237
0
            pLOKEv->mpWindow->SetLastMousePos(pLOKEv->maMouseEvent.GetPosPixel());
1238
0
            pLOKEv->mpWindow->MouseButtonDown(pLOKEv->maMouseEvent);
1239
            // Invoke the context menu
1240
0
            if (pLOKEv->maMouseEvent.GetButtons() & MOUSE_RIGHT)
1241
0
            {
1242
0
                const CommandEvent aCEvt(pLOKEv->maMouseEvent.GetPosPixel(), CommandEventId::ContextMenu, true, nullptr);
1243
0
                pLOKEv->mpWindow->Command(aCEvt);
1244
0
            }
1245
0
            break;
1246
0
        case VclEventId::WindowMouseButtonUp:
1247
0
            pLOKEv->mpWindow->SetLastMousePos(pLOKEv->maMouseEvent.GetPosPixel());
1248
0
            pLOKEv->mpWindow->MouseButtonUp(pLOKEv->maMouseEvent);
1249
1250
            // sometimes MouseButtonDown captures mouse and starts tracking, and VCL
1251
            // will not take care of releasing that with tiled rendering
1252
0
            if (pLOKEv->mpWindow->IsTracking())
1253
0
                pLOKEv->mpWindow->EndTracking();
1254
1255
0
            break;
1256
0
        case VclEventId::WindowMouseMove:
1257
0
            pLOKEv->mpWindow->SetLastMousePos(pLOKEv->maMouseEvent.GetPosPixel());
1258
0
            pLOKEv->mpWindow->MouseMove(pLOKEv->maMouseEvent);
1259
0
            pLOKEv->mpWindow->RequestHelp(HelpEvent{
1260
0
                pLOKEv->mpWindow->OutputToScreenPixel(pLOKEv->maMouseEvent.GetPosPixel()),
1261
0
                HelpEventMode::QUICK }); // If needed, HelpEventMode should be taken from a config
1262
0
            break;
1263
0
        case VclEventId::ExtTextInput:
1264
0
        case VclEventId::EndExtTextInput:
1265
0
            pLOKEv->mpWindow->PostExtTextInputEvent(pLOKEv->mnEvent, pLOKEv->maText);
1266
0
            break;
1267
0
        default:
1268
0
            assert(false);
1269
0
            break;
1270
0
        }
1271
0
    }
1272
1273
    void postEventAsync(LOKAsyncEventData *pEvent)
1274
0
    {
1275
0
        if (!pEvent->mpWindow || pEvent->mpWindow->isDisposed())
1276
0
        {
1277
0
            SAL_WARN("vcl", "Async event post - but no valid window as destination " << pEvent->mpWindow.get());
1278
0
            delete pEvent;
1279
0
            return;
1280
0
        }
1281
1282
0
        pEvent->mnView = SfxLokHelper::getCurrentView();
1283
0
        if (vcl::lok::isUnipoll())
1284
0
        {
1285
0
            if (!Application::IsMainThread())
1286
0
                SAL_WARN("lok", "Posting event directly but not called from main thread!");
1287
0
            LOKPostAsyncEvent(pEvent, nullptr);
1288
0
        }
1289
0
        else
1290
0
            Application::PostUserEvent(LINK_NONMEMBER(pEvent, LOKPostAsyncEvent));
1291
0
    }
1292
}
1293
1294
void SfxLokHelper::postKeyEventAsync(const VclPtr<vcl::Window> &xWindow,
1295
                                     int nType, int nCharCode, int nKeyCode, int nRepeat)
1296
0
{
1297
0
    LOKAsyncEventData* pLOKEv = new LOKAsyncEventData;
1298
0
    switch (nType)
1299
0
    {
1300
0
    case LOK_KEYEVENT_KEYINPUT:
1301
0
        pLOKEv->mnEvent = VclEventId::WindowKeyInput;
1302
0
        break;
1303
0
    case LOK_KEYEVENT_KEYUP:
1304
0
        pLOKEv->mnEvent = VclEventId::WindowKeyUp;
1305
0
        break;
1306
0
    default:
1307
0
        assert(false);
1308
0
    }
1309
0
    pLOKEv->maKeyEvent = KeyEvent(nCharCode, nKeyCode, nRepeat);
1310
0
    pLOKEv->mpWindow = xWindow;
1311
0
    postEventAsync(pLOKEv);
1312
0
}
1313
1314
void SfxLokHelper::setBlockedCommandList(int nViewId, const char* blockedCommandList)
1315
0
{
1316
0
    SfxViewShell* pViewShell = SfxLokHelper::getViewOfId(nViewId);
1317
1318
0
    if(pViewShell)
1319
0
    {
1320
0
        pViewShell->setBlockedCommandList(blockedCommandList);
1321
0
    }
1322
0
}
1323
1324
void SfxLokHelper::postExtTextEventAsync(const VclPtr<vcl::Window> &xWindow,
1325
                                         int nType, const OUString &rText)
1326
0
{
1327
0
    LOKAsyncEventData* pLOKEv = new LOKAsyncEventData;
1328
0
    switch (nType)
1329
0
    {
1330
0
    case LOK_EXT_TEXTINPUT:
1331
0
        pLOKEv->mnEvent = VclEventId::ExtTextInput;
1332
0
        pLOKEv->maText = rText;
1333
0
        break;
1334
0
    case LOK_EXT_TEXTINPUT_END:
1335
0
        pLOKEv->mnEvent = VclEventId::EndExtTextInput;
1336
0
        pLOKEv->maText = "";
1337
0
        break;
1338
0
    default:
1339
0
        assert(false);
1340
0
    }
1341
0
    pLOKEv->mpWindow = xWindow;
1342
0
    postEventAsync(pLOKEv);
1343
0
}
1344
1345
void SfxLokHelper::postMouseEventAsync(const VclPtr<vcl::Window> &xWindow, LokMouseEventData const & rLokMouseEventData)
1346
0
{
1347
0
    LOKAsyncEventData* pLOKEv = new LOKAsyncEventData;
1348
0
    switch (rLokMouseEventData.mnType)
1349
0
    {
1350
0
    case LOK_MOUSEEVENT_MOUSEBUTTONDOWN:
1351
0
        pLOKEv->mnEvent = VclEventId::WindowMouseButtonDown;
1352
0
        break;
1353
0
    case LOK_MOUSEEVENT_MOUSEBUTTONUP:
1354
0
        pLOKEv->mnEvent = VclEventId::WindowMouseButtonUp;
1355
0
        break;
1356
0
    case LOK_MOUSEEVENT_MOUSEMOVE:
1357
0
        pLOKEv->mnEvent = VclEventId::WindowMouseMove;
1358
0
        break;
1359
0
    default:
1360
0
        assert(false);
1361
0
    }
1362
1363
    // no reason - just always true so far.
1364
0
    assert (rLokMouseEventData.meModifiers == MouseEventModifiers::SIMPLECLICK);
1365
1366
0
    pLOKEv->maMouseEvent = MouseEvent(rLokMouseEventData.maPosition, rLokMouseEventData.mnCount,
1367
0
                                      rLokMouseEventData.meModifiers, rLokMouseEventData.mnButtons,
1368
0
                                      rLokMouseEventData.mnModifier);
1369
0
    if (rLokMouseEventData.maLogicPosition)
1370
0
    {
1371
0
        pLOKEv->maMouseEvent.setLogicPosition(*rLokMouseEventData.maLogicPosition);
1372
0
    }
1373
0
    pLOKEv->mpWindow = xWindow;
1374
0
    postEventAsync(pLOKEv);
1375
0
}
1376
1377
void SfxLokHelper::dumpState(rtl::OStringBuffer &rState)
1378
0
{
1379
0
    SfxViewShell* pShell = SfxViewShell::Current();
1380
0
    sal_Int32 nDocId = pShell ? static_cast<sal_Int32>(pShell->GetDocId().get()) : -1;
1381
1382
0
    rState.append("\n\tDocId:\t");
1383
0
    rState.append(nDocId);
1384
1385
0
    if (nDocId < 0)
1386
0
        return;
1387
1388
0
    rState.append("\n\tViewCount:\t");
1389
0
    rState.append(static_cast<sal_Int32>(getViewsCount(nDocId)));
1390
1391
0
    const SfxViewShell* const pCurrentViewShell = SfxViewShell::Current();
1392
0
    SfxViewShell* pViewShell = SfxViewShell::GetFirst();
1393
0
    while (pViewShell)
1394
0
    {
1395
0
        if (pCurrentViewShell == nullptr || pViewShell->GetDocId() == pCurrentViewShell-> GetDocId())
1396
0
            pViewShell->dumpLibreOfficeKitViewState(rState);
1397
1398
0
        pViewShell = SfxViewShell::GetNext(*pViewShell);
1399
0
    }
1400
0
}
1401
1402
bool SfxLokHelper::testInPlaceComponentMouseEventHit(SfxViewShell* pViewShell, int nType, int nX,
1403
                                                     int nY, int nCount, int nButtons,
1404
                                                     int nModifier, double fScaleX, double fScaleY,
1405
                                                     bool bNegativeX)
1406
0
{
1407
    // In LOK RTL mode draw/svx operates in negative X coordinates
1408
    // But the coordinates from client is always positive, so negate nX.
1409
0
    if (bNegativeX)
1410
0
        nX = -nX;
1411
1412
    // check if the user hit a chart/math object which is being edited by this view
1413
0
    if (LokChartHelper aChartHelper(pViewShell, bNegativeX);
1414
0
        aChartHelper.postMouseEvent(nType, nX, nY, nCount, nButtons, nModifier, fScaleX, fScaleY))
1415
0
        return true;
1416
1417
0
    if (LokStarMathHelper aMathHelper(pViewShell);
1418
0
        aMathHelper.postMouseEvent(nType, nX, nY, nCount, nButtons, nModifier, fScaleX, fScaleY))
1419
0
        return true;
1420
1421
    // check if the user hit a chart which is being edited by someone else
1422
    // and, if so, skip current mouse event
1423
0
    if (nType != LOK_MOUSEEVENT_MOUSEMOVE)
1424
0
    {
1425
0
        if (LokChartHelper::HitAny({nX, nY}, bNegativeX))
1426
0
            return true;
1427
0
    }
1428
1429
0
    return false;
1430
0
}
1431
1432
VclPtr<vcl::Window> SfxLokHelper::getInPlaceDocWindow(SfxViewShell* pViewShell)
1433
0
{
1434
0
    if (VclPtr<vcl::Window> pWindow = LokChartHelper(pViewShell).GetWindow())
1435
0
        return pWindow;
1436
0
    if (VclPtr<vcl::Window> pWindow = LokStarMathHelper(pViewShell).GetWidgetWindow())
1437
0
        return pWindow;
1438
0
    return {};
1439
0
}
1440
1441
void SfxLokHelper::sendNetworkAccessError(std::string_view rAction)
1442
0
{
1443
0
    tools::JsonWriter aWriter;
1444
0
    aWriter.put("code", static_cast<sal_uInt32>(
1445
0
        ErrCode(ErrCodeArea::Inet, sal_uInt16(ErrCodeClass::Access))));
1446
0
    aWriter.put("kind", "network");
1447
0
    aWriter.put("cmd", rAction);
1448
1449
0
    SfxViewShell* pViewShell = SfxViewShell::Current();
1450
0
    if (pViewShell)
1451
0
    {
1452
0
        pViewShell->libreOfficeKitViewCallback(
1453
0
            LOK_CALLBACK_ERROR, aWriter.finishAndGetAsOString());
1454
0
    }
1455
0
}
1456
1457
SfxLokLanguageGuard::SfxLokLanguageGuard(const SfxViewShell* pNewShell)
1458
0
    : m_bSetLanguage(false)
1459
0
    , m_pOldShell(nullptr)
1460
0
{
1461
0
    m_pOldShell = SfxViewShell::Current();
1462
0
    if (!comphelper::LibreOfficeKit::isActive() || !pNewShell || pNewShell == m_pOldShell)
1463
0
    {
1464
0
        return;
1465
0
    }
1466
1467
    // The current view ID is not the one that belongs to this frame, update
1468
    // language/locale.
1469
0
    comphelper::LibreOfficeKit::setLanguageTag(pNewShell->GetLOKLanguageTag());
1470
0
    comphelper::LibreOfficeKit::setLocale(pNewShell->GetLOKLocale());
1471
0
    m_bSetLanguage = true;
1472
0
}
1473
1474
SfxLokLanguageGuard::~SfxLokLanguageGuard()
1475
0
{
1476
0
    if (!m_bSetLanguage || !m_pOldShell)
1477
0
    {
1478
0
        return;
1479
0
    }
1480
1481
0
    comphelper::LibreOfficeKit::setLanguageTag(m_pOldShell->GetLOKLanguageTag());
1482
0
    comphelper::LibreOfficeKit::setLocale(m_pOldShell->GetLOKLocale());
1483
0
}
1484
1485
LOKEditViewHistory::EditViewHistoryMap LOKEditViewHistory::maEditViewHistory;
1486
1487
1488
void LOKEditViewHistory::Update(bool bRemove)
1489
0
{
1490
0
    if (!comphelper::LibreOfficeKit::isActive())
1491
0
        return;
1492
1493
0
    SfxViewShell* pViewShell = SfxViewShell::Current();
1494
0
    if (pViewShell)
1495
0
    {
1496
0
        int nDocId = pViewShell->GetDocId().get();
1497
0
        if (maEditViewHistory.find(nDocId) != maEditViewHistory.end())
1498
0
            maEditViewHistory[nDocId].remove(pViewShell);
1499
0
        if (!bRemove)
1500
0
        {
1501
0
            maEditViewHistory[nDocId].push_back(pViewShell);
1502
0
            if (maEditViewHistory[nDocId].size() > 10)
1503
0
                maEditViewHistory[nDocId].pop_front();
1504
0
        }
1505
0
    }
1506
0
}
1507
1508
ViewShellList LOKEditViewHistory::GetHistoryForDoc(ViewShellDocId aDocId)
1509
0
{
1510
0
    int nDocId = aDocId.get();
1511
0
    ViewShellList aResult;
1512
0
    if (maEditViewHistory.find(nDocId) != maEditViewHistory.end())
1513
0
        aResult = maEditViewHistory.at(nDocId);
1514
0
    return aResult;
1515
0
}
1516
1517
 ViewShellList LOKEditViewHistory::GetSortedViewsForDoc(ViewShellDocId aDocId)
1518
0
 {
1519
0
     ViewShellList aEditViewHistoryForDoc = LOKEditViewHistory::GetHistoryForDoc(aDocId);
1520
     // all views where document is loaded
1521
0
     ViewShellList aCurrentDocViewList;
1522
     // active views that are listed in the edit history
1523
0
     ViewShellList aEditedViewList;
1524
1525
     // Populate aCurrentDocViewList and aEditedViewList
1526
0
     SfxViewShell* pViewShell = SfxViewShell::GetFirst();
1527
0
     while (pViewShell)
1528
0
     {
1529
0
         if (pViewShell->GetDocId() == aDocId)
1530
0
         {
1531
0
             if (aEditViewHistoryForDoc.empty() ||
1532
0
                 std::find(aEditViewHistoryForDoc.begin(), aEditViewHistoryForDoc.end(),
1533
0
                           pViewShell) == aEditViewHistoryForDoc.end())
1534
0
             {
1535
                 // append views not listed in the edit history;
1536
                 // the edit history is limited to 10 views,
1537
                 // so it could miss some view where in place editing is occurring
1538
0
                 aCurrentDocViewList.push_back(pViewShell);
1539
0
             }
1540
0
             else
1541
0
             {
1542
                 // view is listed in the edit history
1543
0
                 aEditedViewList.push_back(pViewShell);
1544
0
             }
1545
0
         }
1546
0
         pViewShell = SfxViewShell::GetNext(*pViewShell);
1547
0
     }
1548
1549
     // in case some no more active view needs to be removed from the history
1550
0
     aEditViewHistoryForDoc.remove_if(
1551
0
         [&aEditedViewList](SfxViewShell* pHistoryItem) {
1552
0
             return std::find(aEditedViewList.begin(), aEditedViewList.end(), pHistoryItem) == aEditedViewList.end();
1553
0
         });
1554
1555
     // place views belonging to the edit history at the end
1556
0
     aCurrentDocViewList.splice(aCurrentDocViewList.end(), aEditViewHistoryForDoc);
1557
1558
0
     return aCurrentDocViewList;
1559
0
 }
1560
1561
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */