#!/usr/bin/env python3
# -*- coding: utf-8 -*-

#
# Author: Serg Kolo , contact: 1047481448@qq.com
# Date: August 25 , 2016
# Purpose: appindicator for easy switching between monitor resolutions
# Tested on: Ubuntu 16.04 LTS
#
#
# Licensed under The MIT License (MIT).
# See included LICENSE file or the notice below.
#
# Copyright © 2016 Sergiy Kolodyazhnyy
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import gi
gi.require_version('AyatanaAppIndicator3', '0.1')
from gi.repository import GLib as glib
from gi.repository import AyatanaAppIndicator3 as appindicator
from gi.repository import Gtk as gtk
from collections import OrderedDict
import subprocess
import os

# TODO: add right-of and left-of functions to each menu entry
# 
class XrandrIndicator(object):

    def __init__(self):
        self.app = appindicator.Indicator.new(
            'xrandr-indicator', "",
            appindicator.IndicatorCategory.HARDWARE
            )
        self.app.set_status(appindicator.IndicatorStatus.ACTIVE)
        self.make_menu()
        self.app.set_icon("xrandr-indicator")


    def add_submenu(self,top_menu,label):
        menuitem = gtk.MenuItem(label)
        submenu = gtk.Menu()
        menuitem.set_submenu(submenu)
        top_menu.append(menuitem)
        menuitem.show()
        return submenu

    def add_menu_item(self, menu_obj, item_type, image, label, action, args):
        """ dynamic function that can add menu items depending on
            the item type and other arguments"""
        menu_item, icon = None, None
        if item_type is gtk.ImageMenuItem and label:
            menu_item = gtk.ImageMenuItem.new_with_label(label)
            menu_item.set_always_show_image(True)
            if '/' in image:
                icon = gtk.Image.new_from_file(image)
            else:
                icon = gtk.Image.new_from_icon_name(image, 48)
            menu_item.set_image(icon)
        elif item_type is gtk.ImageMenuItem and not label:
            menu_item = gtk.ImageMenuItem()
            menu_item.set_always_show_image(True)
            if '/' in image:
                icon = gtk.Image.new_from_file(image)
            else:
                icon = gtk.Image.new_from_icon_name(image, 16)
            menu_item.set_image(icon)
        elif item_type is gtk.MenuItem:
            menu_item = gtk.MenuItem(label)
        elif item_type is gtk.SeparatorMenuItem:
            menu_item = gtk.SeparatorMenuItem()
        if action:
            menu_item.connect('activate', action, *args)
        menu_obj.append(menu_item)
        menu_item.show()



    def set_resolution(self,*args):
        out = args[-2]
        mode = args[-1]
        if '*' in args[-1]:
            mode = args[-1].replace('*','')
        self.run_cmd(['xrandr','--output',out,'--mode',mode]) 
        self.make_menu()

    def make_menu(self,*args):
        """ generates entries in the indicator"""
        if hasattr(self, 'app_menu'):
            for item in self.app_menu.get_children():
                self.app_menu.remove(item)

        self.app_menu = gtk.Menu()
        separator_added = None
        # TODO: sort out the mess with sorting
        monitors_dict = self.get_connected_monitors().items()
        sorted_monitors = sorted(monitors_dict,reverse=True)

        positions = ['above','below','right-of','left-of','same-as']


        for monitor,resolutions in OrderedDict(sorted_monitors).items():
            monitor_submenu = self.add_submenu(self.app_menu,monitor)
            
            item = [monitor_submenu,gtk.MenuItem,
                    None,'Resolutions',
                    None,[None] 
            ]
            self.add_menu_item(*item)

            for child in monitor_submenu.get_children(): last = child
            last.set_sensitive(False)

            for res in resolutions:
                #print(res)
                item = [monitor_submenu,gtk.MenuItem,
                        None,res,
                        self.set_resolution,[monitor,res]
                ]
                self.add_menu_item(*item)


            rotation_submenu = self.add_submenu(monitor_submenu,'rotate')
            orientations = ['normal','left','right','inverted']
            for orientation in orientations:
                 item = [ rotation_submenu,gtk.MenuItem,
                          None,orientation,
                          self.rotate,[monitor,orientation]
                 ]
                 self.add_menu_item(*item)
             

            if len(OrderedDict(sorted_monitors).keys()) == 1: break

            item = [monitor_submenu,gtk.MenuItem,
                    None,'Position',
                    None,[None] 
            ]
            self.add_menu_item(*item)
            for child in monitor_submenu.get_children(): last = child
            last.set_sensitive(False)
            
            for position in positions:
                pos_menu = self.add_submenu(monitor_submenu,position)
                for i in OrderedDict(sorted_monitors).keys():
                   if i == monitor : continue
                   pos_item = [ pos_menu,gtk.MenuItem,
                                None,i,self.set_position,[monitor,i,position]
                   ]
                   self.add_menu_item(*pos_item)

        self.update_menu = gtk.MenuItem('Update Menu')
        self.update_menu.connect('activate',self.make_menu)
        self.app_menu.append(self.update_menu)
        self.update_menu.show()
 
        self.quit_app = gtk.MenuItem('Quit')
        self.quit_app.connect('activate', self.quit)
        self.app_menu.append(self.quit_app)
        self.quit_app.show()

        self.app.set_menu(self.app_menu)

    def rotate(self,*args): 
        monitor = args[-2]
        orientation = args[-1]
        subprocess.call(['xrandr','--output',monitor,'--rotate',orientation])

    def set_position(self,*args):
        output = args[-3]
        monitor = args[-2]
        position = '--' + args[-1]
        subprocess.call(['xrandr','--output',output,position,monitor])

    def run(self):
        """ Launches the indicator """
        try:
            gtk.main()
        except KeyboardInterrupt:
            pass

    def quit(self, data=None):
        """ closes indicator """
        gtk.main_quit()

    def run_cmd(self, cmdlist):
        """ Reusable function for running external commands """
        new_env = dict(os.environ)
        new_env['LC_ALL'] = 'C'
        try:
            stdout = subprocess.check_output(cmdlist, env=new_env)
        except subprocess.CalledProcessError:
            pass
        else:
            if stdout:
                return stdout


    def get_connected_monitors(self):
        """ returns dictionary of currently connected
            monitors and their resolutions
        """
        output = self.run_cmd(['xrandr']).decode().split('\n')
        found = None
        monitors = {}
        for item in output:
             if ' connected' in item:
                found = True
                name = item.split()[0]
                monitors[name] = []
             if found and item.startswith(' '):
                resolution = item.split()[0]
                if '*' in item:
                    resolution = resolution + '*' 
                monitors[name].append(resolution)
             if ' disconnected ' in item:
                found = False
        
        return monitors


def main():
    """ defines program entry point """
    indicator = XrandrIndicator()
    indicator.run()

if __name__ == '__main__':
    main()
