Resolves: tdf#142176 under GNOME inhibit logout if there are modified docs

and if we are forced to quit anyway, unset modifications on documents and
terminate.

Change-Id: If4a3aed48a4621950e2d630fdfef34b28ba1b089
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/151575
Tested-by: Jenkins
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
diff --git a/include/sal/log-areas.dox b/include/sal/log-areas.dox
index 15bcf5a..51b005e 100644
--- a/include/sal/log-areas.dox
+++ b/include/sal/log-areas.dox
@@ -506,7 +506,7 @@ certain functionality.
@li @c vcl.quartz
@li @c vcl.schedule - scheduler / main-loop information
@li @c vcl.schedule.deinit
@li @c vcl.screensaverinhibitor
@li @c vcl.sessioninhibitor
@li @c vcl.scrollbar - Scroll Bars
@li @c vcl.se - VCL Session Manager
@li @c vcl.se.debug
diff --git a/solenv/clang-format/excludelist b/solenv/clang-format/excludelist
index 3453747..a4f04ad 100644
--- a/solenv/clang-format/excludelist
+++ b/solenv/clang-format/excludelist
@@ -14978,7 +14978,7 @@ vcl/unx/generic/printer/ppdparser.cxx
vcl/unx/generic/printer/printerinfomanager.cxx
vcl/unx/generic/window/salframe.cxx
vcl/unx/generic/window/salobj.cxx
vcl/unx/generic/window/screensaverinhibitor.cxx
vcl/unx/generic/window/sessioninhibitor.cxx
vcl/unx/gtk3/a11y/atklistener.hxx
vcl/unx/gtk3/a11y/atkwrapper.hxx
vcl/unx/gtk3/a11y/atkaction.cxx
diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk
index 9eca753..a7c1fbc 100644
--- a/vcl/Library_vcl.mk
+++ b/vcl/Library_vcl.mk
@@ -579,7 +579,7 @@ endif

ifeq ($(USING_X11),TRUE)
$(eval $(call gb_Library_add_exception_objects,vcl,\
    vcl/unx/generic/window/screensaverinhibitor \
    vcl/unx/generic/window/sessioninhibitor \
    vcl/unx/generic/printer/cpdmgr \
))

diff --git a/vcl/inc/qt5/QtFrame.hxx b/vcl/inc/qt5/QtFrame.hxx
index b927d36..09abe53 100644
--- a/vcl/inc/qt5/QtFrame.hxx
+++ b/vcl/inc/qt5/QtFrame.hxx
@@ -34,7 +34,7 @@
#include <QtCore/QObject>

#if CHECK_ANY_QT_USING_X11
#include <unx/screensaverinhibitor.hxx>
#include <unx/sessioninhibitor.hxx>
// any better way to get rid of the X11 / Qt type clashes?
#undef Bool
#undef CursorShape
diff --git a/vcl/inc/strings.hrc b/vcl/inc/strings.hrc
index 83beea0..c2e95f2 100644
--- a/vcl/inc/strings.hrc
+++ b/vcl/inc/strings.hrc
@@ -122,6 +122,9 @@
#define STR_QUIRKY                                  NC_("STR_QUIRKY", "Quirky Tests: %1")
#define STR_FAILED                                  NC_("STR_FAILED", "Failed Tests: %1")
#define STR_SKIPPED                                 NC_("STR_SKIPPED", "Skipped Tests: %1")

#define STR_UNSAVED_DOCUMENTS                       NC_("STR_UNSAVED_DOCUMENTS", "There are unsaved documents")

#endif // INCLUDED_VCL_INC_STRINGS_HRC

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/unx/gtk/gtkframe.hxx b/vcl/inc/unx/gtk/gtkframe.hxx
index 1a83a7f..890bcdb 100644
--- a/vcl/inc/unx/gtk/gtkframe.hxx
+++ b/vcl/inc/unx/gtk/gtkframe.hxx
@@ -32,7 +32,7 @@
#include <vcl/idle.hxx>
#include <vcl/sysdata.hxx>
#include <unx/saltype.h>
#include <unx/screensaverinhibitor.hxx>
#include <unx/sessioninhibitor.hxx>

#include <tools/link.hxx>

@@ -187,6 +187,9 @@ class GtkSalFrame final : public SalFrame
#endif
    gulong                          m_nPortalSettingChangedSignalId;
    GDBusProxy*                     m_pSettingsPortal;
    gulong                          m_nSessionClientSignalId;
    GDBusProxy*                     m_pSessionManager;
    GDBusProxy*                     m_pSessionClient;
#if !GTK_CHECK_VERSION(4, 0, 0)
    GdkWindow*                      m_pForeignParent;
    GdkNativeWindow                 m_aForeignParentWindow;
@@ -425,6 +428,8 @@ class GtkSalFrame final : public SalFrame

    void ListenPortalSettings();

    void ListenSessionManager();

    void UpdateGeometryFromEvent(int x_root, int y_root, int nEventX, int nEventY);

public:
@@ -654,6 +659,8 @@ public:

    void SetColorScheme(GVariant* variant);

    void SessionManagerInhibit(bool bStart, ApplicationInhibitFlags eType, std::u16string_view sReason, const char* application_id);

    void DisallowCycleFocusOut();
    bool IsCycleFocusOutDisallowed() const;
    void AllowCycleFocusOut();
diff --git a/vcl/inc/unx/salframe.h b/vcl/inc/unx/salframe.h
index d8a177d..c14c325 100644
--- a/vcl/inc/unx/salframe.h
+++ b/vcl/inc/unx/salframe.h
@@ -24,7 +24,7 @@

#include <unx/saltype.h>
#include <unx/saldisp.hxx>
#include <unx/screensaverinhibitor.hxx>
#include <unx/sessioninhibitor.hxx>
#include <salframe.hxx>
#include <salwtype.hxx>
#include <salinst.hxx>
diff --git a/vcl/inc/unx/screensaverinhibitor.hxx b/vcl/inc/unx/sessioninhibitor.hxx
similarity index 97%
rename from vcl/inc/unx/screensaverinhibitor.hxx
rename to vcl/inc/unx/sessioninhibitor.hxx
index 6cfa3e2..5385cde 100644
--- a/vcl/inc/unx/screensaverinhibitor.hxx
+++ b/vcl/inc/unx/sessioninhibitor.hxx
@@ -31,7 +31,8 @@ class VCL_PLUGIN_PUBLIC SessionManagerInhibitor
{
public:
    void inhibit(bool bInhibit, std::u16string_view sReason, ApplicationInhibitFlags eType,
                 unsigned int window_system_id, std::optional<Display*> pDisplay);
                 unsigned int window_system_id, std::optional<Display*> pDisplay,
                 const char* application_id = nullptr);

private:
    // These are all used as guint, however this header may be included
diff --git a/vcl/unx/generic/window/screensaverinhibitor.cxx b/vcl/unx/generic/window/sessioninhibitor.cxx
similarity index 94%
rename from vcl/unx/generic/window/screensaverinhibitor.cxx
rename to vcl/unx/generic/window/sessioninhibitor.cxx
index b1cfcb3..300df9f 100644
--- a/vcl/unx/generic/window/screensaverinhibitor.cxx
+++ b/vcl/unx/generic/window/sessioninhibitor.cxx
@@ -12,7 +12,7 @@
#include <functional>

#include <unx/gensys.h>
#include <unx/screensaverinhibitor.hxx>
#include <unx/sessioninhibitor.hxx>

#include <X11/Xlib.h>
#include <X11/Xatom.h>
@@ -47,9 +47,10 @@
#include <sal/log.hxx>

void SessionManagerInhibitor::inhibit(bool bInhibit, std::u16string_view sReason, ApplicationInhibitFlags eType,
                                      unsigned int window_system_id, std::optional<Display*> pDisplay)
                                      unsigned int window_system_id, std::optional<Display*> pDisplay,
                                      const char* application_id)
{
    const char* appname = SalGenericSystem::getFrameClassName();
    const char* appname = application_id ? application_id : SalGenericSystem::getFrameClassName();
    const OString aReason = OUStringToOString( sReason, RTL_TEXTENCODING_UTF8 );

    if (eType == APPLICATION_INHIBIT_IDLE)
@@ -85,10 +86,10 @@ static void dbusInhibit( bool bInhibit,
    GError          *error = nullptr;
    GDBusConnection *session_connection = g_bus_get_sync( G_BUS_TYPE_SESSION, nullptr, &error );
    if (session_connection == nullptr) {
        SAL_WARN( "vcl.screensaverinhibitor", "failed to connect to dbus session bus" );
        SAL_WARN( "vcl.sessioninhibitor", "failed to connect to dbus session bus" );

        if (error != nullptr) {
            SAL_WARN( "vcl.screensaverinhibitor", "Error: " << error->message );
            SAL_WARN( "vcl.sessioninhibitor", "Error: " << error->message );
            g_error_free( error );
        }

@@ -107,7 +108,7 @@ static void dbusInhibit( bool bInhibit,
    g_object_unref( G_OBJECT( session_connection ) );

    if (proxy == nullptr) {
        SAL_INFO( "vcl.screensaverinhibitor", "could not get dbus proxy: " << service );
        SAL_INFO( "vcl.sessioninhibitor", "could not get dbus proxy: " << service );
        return;
    }

@@ -128,7 +129,7 @@ static void dbusInhibit( bool bInhibit,
        }
        else
        {
            SAL_INFO( "vcl.screensaverinhibitor", service << ".Inhibit failed");
            SAL_INFO( "vcl.sessioninhibitor", service << ".Inhibit failed");
        }
    }
    else
@@ -142,13 +143,13 @@ static void dbusInhibit( bool bInhibit,
        }
        else
        {
            SAL_INFO( "vcl.screensaverinhibitor", service << ".UnInhibit failed" );
            SAL_INFO( "vcl.sessioninhibitor", service << ".UnInhibit failed" );
        }
    }

    if (error != nullptr)
    {
        SAL_INFO( "vcl.screensaverinhibitor", "Error: " << error->message );
        SAL_INFO( "vcl.sessioninhibitor", "Error: " << error->message );
        g_error_free( error );
    }

diff --git a/vcl/unx/gtk3/gtkframe.cxx b/vcl/unx/gtk3/gtkframe.cxx
index b6b4cd5..ea13a70 100644
--- a/vcl/unx/gtk3/gtkframe.cxx
+++ b/vcl/unx/gtk3/gtkframe.cxx
@@ -31,6 +31,7 @@
#include <sal/log.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <vcl/toolkit/floatwin.hxx>
#include <vcl/toolkit/unowrap.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <vcl/window.hxx>
@@ -42,6 +43,7 @@
#include <X11/Xutil.h>
#include <unx/gtk/gtkbackend.hxx>

#include <strings.hrc>
#include <window.h>

#include <basegfx/vector/b2ivector.hxx>
@@ -62,6 +64,8 @@

#include <com/sun/star/awt/MouseButton.hpp>
#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
#include <com/sun/star/frame/Desktop.hpp>
#include <com/sun/star/util/XModifiable.hpp>

#if !GTK_CHECK_VERSION(4, 0, 0)
#   define GDK_ALT_MASK GDK_MOD1_MASK
@@ -630,7 +634,6 @@ void on_registrar_unavailable( GDBusConnection * /*connection*/,

    SAL_INFO("vcl.unity", "on_registrar_unavailable");

    //pSessionBus = NULL;
    GtkSalFrame* pSalFrame = static_cast< GtkSalFrame* >( user_data );

    SalMenu* pSalMenu = pSalFrame->GetMenu();
@@ -720,6 +723,15 @@ GtkSalFrame::~GtkSalFrame()

        if (m_pSettingsPortal)
            g_object_unref(m_pSettingsPortal);

        if (m_nSessionClientSignalId)
            g_signal_handler_disconnect(m_pSessionClient, m_nSessionClientSignalId);

        if (m_pSessionClient)
            g_object_unref(m_pSessionClient);

        if (m_pSessionManager)
            g_object_unref(m_pSessionManager);
    }

    GtkWidget *pEventWidget = getMouseEventWidget();
@@ -940,7 +952,10 @@ void GtkSalFrame::InitCommon()
    m_nGrabLevel = 0;
    m_bSalObjectSetPosSize = false;
    m_nPortalSettingChangedSignalId = 0;
    m_nSessionClientSignalId = 0;
    m_pSettingsPortal = nullptr;
    m_pSessionManager = nullptr;
    m_pSessionClient = nullptr;

    m_aDamageHandler.handle = this;
    m_aDamageHandler.damaged = ::damaged;
@@ -1417,9 +1432,151 @@ void GtkSalFrame::ListenPortalSettings()

    UpdateDarkMode();

    if (!m_pSettingsPortal)
        return;

    m_nPortalSettingChangedSignalId = g_signal_connect(m_pSettingsPortal, "g-signal", G_CALLBACK(settings_portal_changed_cb), this);
}

static void session_client_response(GDBusProxy* client_proxy)
{
    g_dbus_proxy_call(client_proxy,
                      "EndSessionResponse",
                      g_variant_new ("(bs)", true, ""),
                      G_DBUS_CALL_FLAGS_NONE,
                      G_MAXINT,
                      nullptr, nullptr, nullptr);
}

// unset documents "modify" flag so they won't veto closing
static void clear_modify_and_terminate()
{
    css::uno::Reference<css::uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext();
    uno::Reference<frame::XDesktop> xDesktop(frame::Desktop::create(xContext));
    uno::Reference<css::container::XEnumeration> xComponents = xDesktop->getComponents()->createEnumeration();
    while (xComponents->hasMoreElements())
    {
        css::uno::Reference<css::util::XModifiable> xModifiable(xComponents->nextElement(), css::uno::UNO_QUERY);
        if (xModifiable)
            xModifiable->setModified(false);
    }
    xDesktop->terminate();
}

static void session_client_signal(GDBusProxy* client_proxy, const char*, const char* signal_name,
                                  GVariant* /*parameters*/, gpointer frame)
{
    GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);

    if (g_str_equal (signal_name, "QueryEndSession"))
    {
        css::uno::Reference<css::uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext();
        uno::Reference<frame::XDesktop2> xDesktop(frame::Desktop::create(xContext));

        bool bModified = false;

        // find the XModifiable for this GtkSalFrame
        if (UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper(false))
        {
            VclPtr<vcl::Window> xThisWindow = pThis->GetWindow();
            css::uno::Reference<css::container::XIndexAccess> xList = xDesktop->getFrames();
            sal_Int32 nFrameCount = xList->getCount();
            for (sal_Int32 i = 0; i < nFrameCount; ++i)
            {
                css::uno::Reference<css::frame::XFrame> xFrame;
                xList->getByIndex(i) >>= xFrame;
                if (!xFrame)
                    continue;
                VclPtr<vcl::Window> xWin = pWrapper->GetWindow(xFrame->getContainerWindow());
                if (!xWin)
                   continue;
                if (xWin->GetFrameWindow() != xThisWindow)
                    continue;
                css::uno::Reference<css::frame::XController> xController = xFrame->getController();
                if (!xController)
                    break;
                css::uno::Reference<css::util::XModifiable> xModifiable(xController->getModel(), css::uno::UNO_QUERY);
                if (!xModifiable)
                    break;
                bModified = xModifiable->isModified();
                break;
            }
        }

        pThis->SessionManagerInhibit(bModified, APPLICATION_INHIBIT_LOGOUT, VclResId(STR_UNSAVED_DOCUMENTS),
                                     gtk_window_get_icon_name(GTK_WINDOW(pThis->getWindow())));

        session_client_response(client_proxy);
    }
    else if (g_str_equal (signal_name, "CancelEndSession"))
    {
        // restore back to uninhibited (to set again if queried), so frames
        // that go away before the next logout don't affect that logout
        pThis->SessionManagerInhibit(false, APPLICATION_INHIBIT_LOGOUT, VclResId(STR_UNSAVED_DOCUMENTS),
                                     gtk_window_get_icon_name(GTK_WINDOW(pThis->getWindow())));
    }
    else if (g_str_equal (signal_name, "EndSession"))
    {
        session_client_response(client_proxy);
        clear_modify_and_terminate();
    }
    else if (g_str_equal (signal_name, "Stop"))
    {
        clear_modify_and_terminate();
    }
}

void GtkSalFrame::ListenSessionManager()
{
    EnsureSessionBus();

    if (!pSessionBus)
        return;

    m_pSessionManager = g_dbus_proxy_new_sync(pSessionBus,
                                              G_DBUS_PROXY_FLAGS_NONE,
                                              nullptr,
                                              "org.gnome.SessionManager",
                                              "/org/gnome/SessionManager",
                                              "org.gnome.SessionManager",
                                              nullptr,
                                              nullptr);

    if (!m_pSessionManager)
        return;

    GVariant* res = g_dbus_proxy_call_sync(m_pSessionManager,
                                 "RegisterClient",
                                 g_variant_new ("(ss)", "org.libreoffice", ""),
                                 G_DBUS_CALL_FLAGS_NONE,
                                 G_MAXINT,
                                 nullptr,
                                 nullptr);

    if (!res)
        return;

    gchar* client_path;
    g_variant_get(res, "(o)", &client_path);
    g_variant_unref(res);

    m_pSessionClient = g_dbus_proxy_new_sync(pSessionBus,
                                             G_DBUS_PROXY_FLAGS_NONE,
                                             nullptr,
                                             "org.gnome.SessionManager",
                                             client_path,
                                             "org.gnome.SessionManager.ClientPrivate",
                                             nullptr,
                                             nullptr);

    g_free(client_path);

    if (!m_pSessionClient)
        return;

    m_nSessionClientSignalId = g_signal_connect(m_pSessionClient, "g-signal", G_CALLBACK(session_client_signal), this);
}

void GtkSalFrame::UpdateDarkMode()
{
    g_autoptr (GVariant) value = nullptr;
@@ -1610,6 +1767,9 @@ void GtkSalFrame::Init( SalFrame* pParent, SalFrameStyleFlags nStyle )

        // Listen to portal settings for e.g. prefer dark theme
        ListenPortalSettings();

        // Listen to session manager for e.g. query-end
        ListenSessionManager();
    }
}

@@ -2470,7 +2630,7 @@ void GtkSalFrame::ShowFullScreen( bool bFullScreen, sal_Int32 nScreen )
    }
}

void GtkSalFrame::StartPresentation( bool bStart )
void GtkSalFrame::SessionManagerInhibit(bool bStart, ApplicationInhibitFlags eType, std::u16string_view sReason, const char* application_id)
{
    guint nWindow(0);
    std::optional<Display*> aDisplay;
@@ -2481,9 +2641,13 @@ void GtkSalFrame::StartPresentation( bool bStart )
        aDisplay = gdk_x11_display_get_xdisplay(getGdkDisplay());
    }

    m_SessionManagerInhibitor.inhibit(bStart, u"presentation",
                                      APPLICATION_INHIBIT_IDLE,
                                      nWindow, aDisplay);
    m_SessionManagerInhibitor.inhibit(bStart, sReason, eType,
                                      nWindow, aDisplay, application_id);
}

void GtkSalFrame::StartPresentation( bool bStart )
{
    SessionManagerInhibit(bStart, APPLICATION_INHIBIT_IDLE, u"presentation", nullptr);
}

void GtkSalFrame::SetAlwaysOnTop( bool bOnTop )