#!/usr/bin/python3

# hidpi-daemon: HiDPI daemon to manage HiDPI and LoDPI monitors on X
# Copyright (C) 2017-2018 System76, Inc.
#
# This file is part of `hidpi-daemon`.
#
# `hidpi-daemon` is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# `hidpi-daemon` 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with `hidpi-daemon`; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

"""
HiDPI daemon to workaround X Server scaling limitations.
"""

import time
start_time = time.monotonic()
import os
import sys
import logging

import signal

import gi
gi.require_version('Notify', '0.7')

from gi.repository import Notify
from gi.repository import GLib, Gio

from pydbus import SessionBus


def notification_closed(arg):
    global bus
    global subscription
    if subscription is not None:
        obj = bus.get('com.system76.hidpi')
        args = obj.getstate()

def subscribe(arg):
    global bus
    global subscription
    subscription = bus.subscribe(iface='com.system76.hidpi',
                   signal='state',
                   object='/com/system76/hidpi',
                   signal_fired=signal_received)
    GLib.idle_add(notification_closed, None)

def unsubscribe():
    global subscription
    if subscription is not None:
        subscription.unsubscribe()

def settings_changed(settings, key):
    notification_closed(None)

class HiDPINotification:
    def __init__(self, mode):
        Notify.init("System76 HiDPI Scaling")
        
        self.mode = mode
        
        header = ""
        message = ""
        action = ""
        
        if mode == 'optimized':
            header = "Displays Set to Optimized Mode"
            message = "Select to set resolution and scaling to Native Mode."
            action = "Revert"
        elif mode == 'native':
            header = "Displays Set to Native Mode"
            message = "Select to set resolution and scaling to Optimized Mode."
            action = "Set Uniform Scale"
        elif mode == 'hidpi':
            header = "Displays Set to HiDPI Mode"
            message = "Select for LoDPI, increased performance, and app compatibility."
            action = "Switch to LoDPI"
        else:
            header = "Displays Set to LoDPI Mode"
            message = "Select to set HiDPI for crisper display."
            action = "Switch to HiDPI"
        
        self.settings = Gio.Settings('com.system76.hidpi')
        self.settings.connect('changed', self.settings_changed)
        if self.settings.get_boolean('enable'):
            self.notification = Notify.Notification.new(header, message, "preferences-desktop-display")
            self.handler_id = self.notification.connect("closed", notification_closed)
            self.notification.add_action("default", action, self.notification_action)
            self.notification.show()
        else:
            self.notification = None
            
        GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGTERM, self.exit)
        
    def terminate(self):
        if self.notification is not None:
            self.notification.disconnect(self.handler_id)
            self.notification.close()
            self.notification = None
        
    def exit(self):
        self.terminate()
        os._exit(0)
    
    def settings_changed(self, settings, key):
        if self.notification is not None:
            if not self.settings.get_boolean('enable'):
                self.terminate()
    
    def notification_action(self, notification, action=None, data=None):
        if self.mode == 'lodpi' or self.mode == 'optimized':
            self.settings.set_string('mode', 'hidpi')
        if self.mode == 'hidpi' or self.mode == 'native':
            self.settings.set_string('mode', 'lodpi')


def signal_received(num, object, iface, signal, args):
    global notification
    
    if signal == 'state':
        if notification is not None:
            notification.terminate()
        if 'mixed' not in args[1] and 'hidpi' not in args[1]:
            return
        elif args[0] == 'hidpi':
            if 'mixed' in args[1] and 'pixel-doubling' not in args[2]:
                notification = HiDPINotification('native')
            else:
                notification = HiDPINotification('hidpi')
        else:
            if 'mixed' in args[1] and 'pixel-doubling' not in args[2]:
                notification = HiDPINotification('optimized')
            else:
                notification = HiDPINotification('lodpi')


logging.basicConfig(
    level=logging.DEBUG,
    style='{',
    format='{asctime}  {levelname}  {message}',
)
log = logging.getLogger()

if os.getuid() == 0:
    sys.exit('Error: system76-hidpi-daemon must be run as user')
log.info('**** Process start at monotonic time %r', start_time)

session_type = os.environ.get('XDG_SESSION_TYPE')
if 'x11' not in session_type:
    sys.exit('Error: system76-hidpi-notification must be run on an X11 session.  Stopping...')


notification = None
subscription = None

desktop_session = os.environ.get('XDG_CURRENT_DESKTOP')
if 'GNOME' in desktop_session:
    bus = SessionBus()
    bus.watch_name('com.system76.hidpi', name_appeared=subscribe, name_vanished=unsubscribe)
    
    settings = Gio.Settings('com.system76.hidpi')
    settings.connect('changed', settings_changed)
    
    loop = GLib.MainLoop()
    loop.run()
else:
    sys.exit('Error: system76-hidpi-notification requires a GNOME-based desktop environment.  Stopping...')
