# Xlib.ext.screensaver -- X ScreenSaver extension module
#
#    Copyright (C) 2022 Vladimir Panteleev <git@cy.md>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public License
# as published by the Free Software Foundation; either version 2.1
# 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
#    Free Software Foundation, Inc.,
#    51 Franklin Street,
#    Fifth Floor,
#    Boston, MA 02110-1301 USA

"""This extension allows registering the client as an X screensaver,
or query information about the current screensaver.

For detailed description see any of the following documents.
Protocol specification:
    https://www.x.org/releases/X11R7.7/doc/scrnsaverproto/saver.html
XCB Protocol specification:
    https://cgit.freedesktop.org/xcb/proto/tree/src/screensaver.xml

"""

from Xlib import X
from Xlib.protocol import rq, structs

extname = 'MIT-SCREEN-SAVER'

# Event members
NotifyMask = 1
CycleMask = 2

# Notify state
StateOff = 0
StateOn = 1
StateCycle = 2

# Notify kind
KindBlanked = 0
KindInternal = 1
KindExternal = 2

class QueryVersion(rq.ReplyRequest):
    _request = rq.Struct(
        rq.Card8('opcode'),
        rq.Opcode(0),
        rq.RequestLength(),
        rq.Card8('major_version'),
        rq.Card8('minor_version'),
        rq.Pad(2),
        )

    _reply = rq.Struct(
            rq.ReplyCode(),
            rq.Pad(1),
            rq.Card16('sequence_number'),
            rq.ReplyLength(),
            rq.Card16('major_version'),
            rq.Card16('minor_version'),
            rq.Pad(20),
            )

def query_version(self):
    return QueryVersion(display=self.display,
                        opcode=self.display.get_extension_major(extname),
                        major_version=1,
                        minor_version=0)


class QueryInfo(rq.ReplyRequest):
    _request = rq.Struct(
        rq.Card8('opcode'),
        rq.Opcode(1),
        rq.RequestLength(),
        rq.Drawable('drawable'),
        )

    _reply = rq.Struct(
            rq.ReplyCode(),
            rq.Card8('state'),
            rq.Card16('sequence_number'),
            rq.ReplyLength(),
            rq.Window('saver_window'),
            rq.Card32('til_or_since'),
            rq.Card32('idle'),
            rq.Card32('event_mask'), # rq.Set('event_mask', 4, (NotifyMask, CycleMask)),
            rq.Card8('kind'),
            rq.Pad(7),
            )

def query_info(self):
    return QueryInfo(display=self.display,
                     opcode=self.display.get_extension_major(extname),
                     drawable=self,
                     )


class SelectInput(rq.Request):
    _request = rq.Struct(
        rq.Card8('opcode'),
        rq.Opcode(2),
        rq.RequestLength(),
        rq.Drawable('drawable'),
        rq.Card32('event_mask'), # rq.Set('event_mask', 4, (NotifyMask, CycleMask)),
        )

def select_input(self, mask):
    return SelectInput(display=self.display,
                       opcode=self.display.get_extension_major(extname),
                       drawable=self,
                       event_mask=mask,
                       )


class SetAttributes(rq.Request):
    _request = rq.Struct(
        rq.Card8('opcode'),
        rq.Opcode(3),
        rq.RequestLength(),
        rq.Drawable('drawable'),
        rq.Int16('x'),
        rq.Int16('y'),
        rq.Card16('width'),
        rq.Card16('height'),
        rq.Card16('border_width'),
        rq.Set('window_class', 1, (X.CopyFromParent, X.InputOutput, X.InputOnly)),
        rq.Card8('depth'),
        rq.Card32('visual'),
        structs.WindowValues('attrs'),
        )

def set_attributes(self, x, y, width, height, border_width,
                   window_class = X.CopyFromParent,
                   depth = X.CopyFromParent,
                   visual = X.CopyFromParent,
                   onerror = None,
                   **keys):
    return SetAttributes(display=self.display,
                         onerror = onerror,
                         opcode=self.display.get_extension_major(extname),
                         drawable=self,
                         x = x,
                         y = y,
                         width = width,
                         height = height,
                         border_width = border_width,
                         window_class = window_class,
                         depth = depth,
                         visual = visual,
                         attrs = keys)


class UnsetAttributes(rq.Request):
    _request = rq.Struct(
        rq.Card8('opcode'),
        rq.Opcode(4),
        rq.RequestLength(),
        rq.Drawable('drawable'),
        )

def unset_attributes(self, onerror = None):
    return UnsetAttributes(display=self.display,
                           onerror = onerror,
                           opcode=self.display.get_extension_major(extname),
                           drawable=self)


class Notify(rq.Event):
    _code = None
    _fields = rq.Struct(
        rq.Card8('type'),
        rq.Set('state', 1, (StateOff, StateOn, StateCycle)),
        rq.Card16('sequence_number'),
        rq.Card32('timestamp'),
        rq.Window('root'),
        rq.Window('window'),
        rq.Set('kind', 1, (KindBlanked, KindInternal, KindExternal)),
        rq.Bool('forced'),
        rq.Pad(14),
        )

def init(disp, info):
    disp.extension_add_method('display', 'screensaver_query_version', query_version)
    disp.extension_add_method('drawable', 'screensaver_query_info', query_info)
    disp.extension_add_method('drawable', 'screensaver_select_input', select_input)
    disp.extension_add_method('drawable', 'screensaver_set_attributes', set_attributes)
    disp.extension_add_method('drawable', 'screensaver_unset_attributes', unset_attributes)

    disp.extension_add_event(info.first_event + 0, Notify)
