/* This file is part of the KDE project
   Copyright (C) 1999 David Faure
   Copyright (c) 2003 Oswald Buddenhagen <ossi@kde.org>
   Copyright (c) 2010-2012 Timothy Pearson <kb9vqf@pearsoncomputing.net>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

#include "lockprocess.h"
#include "main.h"
#include "kdesktopsettings.h"

#include <kcmdlineargs.h>
#include <klocale.h>
#include <kglobal.h>
#include <kdebug.h>
#include <kglobalsettings.h>
#include <dcopref.h>
#include <ksimpleconfig.h>

#include <tdmtsak.h>

#include <stdlib.h>

#include <X11/Xlib.h>
#include <fixx11h.h>

#define OPEN_TDMCONFIG_AND_SET_GROUP									\
if( stat( KDE_CONFDIR "/tdm/tdmdistrc" , &st ) == 0) {							\
	tdmconfig = new KSimpleConfig( TQString::fromLatin1( KDE_CONFDIR "/tdm/tdmdistrc" ));		\
}													\
else {													\
	tdmconfig = new KSimpleConfig( TQString::fromLatin1( KDE_CONFDIR "/tdm/tdmrc" ));		\
}													\
tdmconfig->setGroup("X-*-Greeter");

TQXLibWindowList trinity_desktop_lock_hidden_window_list;

// [FIXME] Add GUI configuration checkboxes for these three settings (see kdesktoprc [ScreenSaver] UseUnmanagedLockWindows, DelaySaverStart, and UseTDESAK)
bool trinity_desktop_lock_use_system_modal_dialogs = FALSE;
bool trinity_desktop_lock_delay_screensaver_start = FALSE;
bool trinity_desktop_lock_use_sak = FALSE;
bool trinity_desktop_lock_hide_active_windows = FALSE;

bool trinity_desktop_lock_forced = FALSE;

bool signalled_forcelock;
bool signalled_dontlock;
bool signalled_securedialog;
bool signalled_blank;
bool signalled_run;
bool in_internal_mode = FALSE;

bool MyApp::x11EventFilter( XEvent *ev )
{
    if (ev->type == ButtonPress || ev->type == ButtonRelease || ev->type == MotionNotify) {
        emit mouseInteraction(ev);
    }
    if (ev->type == XKeyPress || ev->type == ButtonPress) {
        emit activity();
    }
    else if (ev->type == MotionNotify) {
        time_t tick = time( 0 );
        if (tick != lastTick) {
            lastTick = tick;
            emit activity();
        }
    }
    else if (ev->type == MapNotify) {
        // HACK
        // Hide all tooltips and notification windows
        XMapEvent map_event = ev->xmap;
        XWindowAttributes childAttr;
        Window childTransient;
        if (XGetWindowAttributes(map_event.display, map_event.window, &childAttr) && XGetTransientForHint(map_event.display, map_event.window, &childTransient)) {
            if((childAttr.map_state == IsViewable) && (childAttr.override_redirect) && (childTransient)) {
                if (!trinity_desktop_lock_hidden_window_list.contains(map_event.window)) {
                    trinity_desktop_lock_hidden_window_list.append(map_event.window);
                }
                XLowerWindow(map_event.display, map_event.window);
                XFlush(map_event.display);
            }
        }
    }
    else if (ev->type == VisibilityNotify) {
        // HACK
        // Hide all tooltips and notification windows
        XVisibilityEvent visibility_event = ev->xvisibility;
        XWindowAttributes childAttr;
        Window childTransient;
        if ((visibility_event.state == VisibilityUnobscured) || (visibility_event.state == VisibilityPartiallyObscured)) {
            if (XGetWindowAttributes(visibility_event.display, visibility_event.window, &childAttr) && XGetTransientForHint(visibility_event.display, visibility_event.window, &childTransient)) {
                if((childAttr.map_state == IsViewable) && (childAttr.override_redirect) && (childTransient)) {
                    if (!trinity_desktop_lock_hidden_window_list.contains(visibility_event.window)) {
                        trinity_desktop_lock_hidden_window_list.append(visibility_event.window);
                    }
                    XLowerWindow(visibility_event.display, visibility_event.window);
                    XFlush(visibility_event.display);
                }
            }
        }
    }
    else if (ev->type == CreateNotify) {
        // HACK
        // Close all tooltips and notification windows
        XCreateWindowEvent create_event = ev->xcreatewindow;
        XWindowAttributes childAttr;
        Window childTransient;
        if (XGetWindowAttributes(create_event.display, create_event.window, &childAttr) && XGetTransientForHint(create_event.display, create_event.window, &childTransient)) {
            if ((childAttr.override_redirect) && (childTransient)) {
                if (!trinity_desktop_lock_hidden_window_list.contains(create_event.window)) {
                    trinity_desktop_lock_hidden_window_list.append(create_event.window);
                }
                XLowerWindow(create_event.display, create_event.window);
                XFlush(create_event.display);
            }
        }
    }
    else if (ev->type == DestroyNotify) {
        XDestroyWindowEvent destroy_event = ev->xdestroywindow;
        if (trinity_desktop_lock_hidden_window_list.contains(destroy_event.window)) {
            trinity_desktop_lock_hidden_window_list.remove(destroy_event.window);
        }
    }
#if 0
    else if (ev->type == CreateNotify) {
        // HACK
        // Close all tooltips and notification windows
        XCreateWindowEvent create_event = ev->xcreatewindow;
        XWindowAttributes childAttr;
        Window childTransient;
        if (XGetWindowAttributes(create_event.display, create_event.window, &childAttr) && XGetTransientForHint(create_event.display, create_event.window, &childTransient)) {
            if ((childAttr.override_redirect) && (childTransient)) {
                XDestroyWindow(create_event.display, create_event.window);
            }
        }
    }
#endif
    return TDEApplication::x11EventFilter( ev );
}


static KCmdLineOptions options[] =
{
   { "forcelock", I18N_NOOP("Force session locking"), 0 },
   { "dontlock", I18N_NOOP("Only start screensaver"), 0 },
   { "securedialog", I18N_NOOP("Launch the secure dialog"), 0 },
   { "blank", I18N_NOOP("Only use the blank screensaver"), 0 },
   { "internal <pid>", I18N_NOOP("TDE internal command for background process loading"), 0 },
   KCmdLineLastOption
};

void restore_hidden_override_redirect_windows() {
    TQXLibWindowList::iterator it;
    for (it = trinity_desktop_lock_hidden_window_list.begin(); it != trinity_desktop_lock_hidden_window_list.end(); ++it) {
        Window win = *it;
        XRaiseWindow(tqt_xdisplay(), win);
    }
}

static void sigusr1_handler(int)
{
    signalled_forcelock = TRUE;
}

static void sigusr2_handler(int)
{
    signalled_dontlock = TRUE;
}

static void sigusr3_handler(int)
{
    signalled_securedialog = TRUE;
}

static void sigusr4_handler(int)
{
    signalled_blank = TRUE;
}

static void sigusr5_handler(int)
{
    signalled_run = TRUE;
}

static int trapXErrors(Display *, XErrorEvent *)
{
    return 0;
}

// -----------------------------------------------------------------------------

int main( int argc, char **argv )
{
    KLocale::setMainCatalogue("kdesktop");

    TDECmdLineArgs::init( argc, argv, "kdesktop_lock", I18N_NOOP("KDesktop Locker"), I18N_NOOP("Session Locker for KDesktop"), "2.1" );
    TDECmdLineArgs::addCmdLineOptions( options );
    TDECmdLineArgs *args = TDECmdLineArgs::parsedArgs();

    putenv(strdup("SESSION_MANAGER="));

    TDEApplication::disableAutoDcopRegistration(); // not needed

    XSetErrorHandler(trapXErrors);

    while (1 == 1) {
        signalled_forcelock = FALSE;
        signalled_dontlock = FALSE;
        signalled_securedialog = FALSE;
        signalled_blank = FALSE;
        signalled_run = FALSE;

        int kdesktop_screen_number = 0;
        int starting_screen = 0;

        bool child = false;
        int parent_connection = 0; // socket to the parent saver
        TQValueList<int> child_sockets;

        if (KGlobalSettings::isMultiHead())
        {
            Display *dpy = XOpenDisplay(NULL);
            if (! dpy) {
                fprintf(stderr,
                        "%s: FATAL ERROR: couldn't open display '%s'\n",
                        argv[0], XDisplayName(NULL));
                exit(1);
            }

            int number_of_screens = ScreenCount(dpy);
            starting_screen = kdesktop_screen_number = DefaultScreen(dpy);
            int pos;
            TQCString display_name = XDisplayString(dpy);
            XCloseDisplay(dpy);
            kdDebug() << "screen " << number_of_screens << " " << kdesktop_screen_number << " " << display_name << " " << starting_screen << endl;
            dpy = 0;

            if ((pos = display_name.findRev('.')) != -1)
                display_name.remove(pos, 10);

            TQCString env;
            if (number_of_screens != 1) {
                for (int i = 0; i < number_of_screens; i++) {
                    if (i != starting_screen) {
                        int fd[2];
                        if (pipe(fd)) {
                            perror("pipe");
                            break;
                        }
                        if (fork() == 0) {
                            child = true;
                            kdesktop_screen_number = i;
                            parent_connection = fd[0];
                            // break here because we are the child process, we don't
                            // want to fork() anymore
                            break;
                        } else {
                            child_sockets.append(fd[1]);
                        }
                    }
                }

                env.sprintf("DISPLAY=%s.%d", display_name.data(),
                            kdesktop_screen_number);
                kdDebug() << "env " << env << endl;

                if (putenv(strdup(env.data()))) {
                    fprintf(stderr,
                            "%s: WARNING: unable to set DISPLAY environment variable\n",
                            argv[0]);
                    perror("putenv()");
                }
            }
        }

        MyApp app;
        kdDebug() << "app " << kdesktop_screen_number << " " << starting_screen << " " << child << " " << child_sockets.count() << " " << parent_connection << endl;
        app.disableSessionManagement();
        KGlobal::locale()->insertCatalogue("libdmctl");

        struct stat st;
        KSimpleConfig* tdmconfig;
        OPEN_TDMCONFIG_AND_SET_GROUP
#ifdef BUILD_TSAK
        trinity_desktop_lock_use_sak = tdmconfig->readBoolEntry("UseSAK", true);
#else
        trinity_desktop_lock_use_sak = false;
#endif
        LockProcess process;

        // Start loading core functions, such as the desktop wallpaper interface
        app.processEvents();

        if (args->isSet( "internal" )) {
            while (signalled_run == FALSE) {
                sigset_t new_mask;
                struct sigaction act;

                in_internal_mode = TRUE;

                // handle SIGUSR1
                act.sa_handler= sigusr1_handler;
                sigemptyset(&(act.sa_mask));
                sigaddset(&(act.sa_mask), SIGUSR1);
                act.sa_flags = 0;
                sigaction(SIGUSR1, &act, 0L);
                // handle SIGUSR2
                act.sa_handler= sigusr2_handler;
                sigemptyset(&(act.sa_mask));
                sigaddset(&(act.sa_mask), SIGUSR2);
                act.sa_flags = 0;
                sigaction(SIGUSR2, &act, 0L);
                // handle SIGWINCH (an ersatz SIGUSR3)
                act.sa_handler= sigusr3_handler;
                sigemptyset(&(act.sa_mask));
                sigaddset(&(act.sa_mask), SIGWINCH);
                act.sa_flags = 0;
                sigaction(SIGWINCH, &act, 0L);
                // handle SIGTTIN (an ersatz SIGUSR4)
                act.sa_handler= sigusr4_handler;
                sigemptyset(&(act.sa_mask));
                sigaddset(&(act.sa_mask), SIGTTIN);
                act.sa_flags = 0;
                sigaction(SIGTTIN, &act, 0L);
                // handle SIGTTOU (an ersatz SIGUSR5)
                act.sa_handler= sigusr5_handler;
                sigemptyset(&(act.sa_mask));
                sigaddset(&(act.sa_mask), SIGTTOU);
                act.sa_flags = 0;
                sigaction(SIGTTOU, &act, 0L);

                // initialize the signal masks
                sigfillset(&new_mask);
                sigdelset(&new_mask,SIGUSR1);
                sigdelset(&new_mask,SIGUSR2);
                sigdelset(&new_mask,SIGWINCH);
                sigdelset(&new_mask,SIGTTIN);
                sigdelset(&new_mask,SIGTTOU);

                // wait for SIGUSR1, SIGUSR2, SIGWINCH, SIGTTIN, or SIGTTOU
                sigsuspend(&new_mask);
            }
        }

        // load settings here so that they actually reflect reality
        // there is no way to force a reload once KDesktopSettings::instance has been called!
        // we need to read from the right rc file - possibly taking screen number in account
        KDesktopSettings::instance("kdesktoprc");
        trinity_desktop_lock_use_system_modal_dialogs = !KDesktopSettings::useUnmanagedLockWindows();
        trinity_desktop_lock_delay_screensaver_start = KDesktopSettings::delaySaverStart();
        if (trinity_desktop_lock_use_system_modal_dialogs) {
#ifdef BUILD_TSAK
            trinity_desktop_lock_use_sak = tdmconfig->readBoolEntry("UseSAK", true);
#else
            trinity_desktop_lock_use_sak = false;
#endif
        }
        else {
            trinity_desktop_lock_use_sak = false;			// If SAK is enabled with unmanaged windows, the SAK dialog will never close and will "burn in" the screen
            trinity_desktop_lock_delay_screensaver_start = false;	// If trinity_desktop_lock_delay_screensaver_start is true with unmanaged windows, the lock dialog may never appear
        }
        trinity_desktop_lock_hide_active_windows = KDesktopSettings::hideActiveWindowsFromSaver();

        delete tdmconfig;

        if (args->isSet( "forcelock" ) || (signalled_forcelock == TRUE)) {
            trinity_desktop_lock_forced = TRUE;
        }

        process.init(child, (args->isSet( "blank" ) || (signalled_blank == TRUE)));
        if (!child) {
            process.setChildren(child_sockets);
        }
        else {
            process.setParent(parent_connection);
        }

        bool rt;
        bool sig = false;
        if( (((!child) && (args->isSet( "forcelock" ))) || (signalled_forcelock == TRUE))) {
            rt = process.lock();
            sig = true;
        }
        else if( child || (args->isSet( "dontlock" ) || (signalled_dontlock == TRUE))) {
            rt = process.dontLock();
        }
        else if( child || (args->isSet( "securedialog" ) || (signalled_securedialog == TRUE))) {
            int retcode = tde_sak_verify_calling_process();
            if (retcode == 0) {
                rt = process.runSecureDialog();
            }
            else {
                return 1;
            }
        }
        else {
            rt = process.defaultSave();
        }
        if (!rt) {
            return 0;
        }

        if( sig )
        {
            DCOPRef ref( "kdesktop", "KScreensaverIface");
            ref.send( "saverLockReady" );
        }

        if (in_internal_mode == FALSE) {
            trinity_desktop_lock_hidden_window_list.clear();
            int ret = app.exec();
            restore_hidden_override_redirect_windows();
            return ret;
        }
        else {
            pid_t kdesktop_pid = atoi(args->getOption( "internal" ));
            if (kill(kdesktop_pid, SIGUSR2) < 0) {
                // The controlling kdesktop process probably died.  Commit suicide...
                return 12;
            }
            trinity_desktop_lock_hidden_window_list.clear();
            app.exec();
            restore_hidden_override_redirect_windows();
            if (kill(kdesktop_pid, SIGUSR1) < 0) {
                // The controlling kdesktop process probably died.  Commit suicide...
                return 12;
            }

            // FIXME
            // We should not have to return (restart) at all,
            // but it seems that some X11 connections are left active,
            // preventing the lock process from restarting properly in the while() loop above.
            return 0;
        }
    }
}

#include "main.moc"