Advertisement
Guest User

Untitled

a guest
Apr 8th, 2018
516
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 17.88 KB | None | 0 0
  1. #!/usr/bin/python3
  2.  
  3. import ctypes
  4. import os
  5. import configparser
  6. import xdg
  7. import pygame
  8. import i3ipc
  9. import copy
  10. import signal
  11. import sys
  12. import traceback
  13. import pprint
  14. import time
  15. from threading import Thread
  16. from PIL import Image, ImageDraw
  17.  
  18. from xdg.BaseDirectory import xdg_config_home
  19.  
  20. pp = pprint.PrettyPrinter(indent=4)
  21.  
  22. global_updates_running = True
  23. global_knowledge = {'active': 0}
  24.  
  25. pygame.display.init()
  26. pygame.font.init()
  27. i3 = i3ipc.Connection()
  28.  
  29. screenshot_lib = 'prtscn.so'
  30. screenshot_lib_path = os.path.dirname(os.path.abspath(__file__)) + os.path.sep + screenshot_lib
  31. grab = ctypes.CDLL(screenshot_lib_path)
  32.  
  33. def signal_quit(signal, frame):
  34.     print("Shutting down...")
  35.     pygame.display.quit()
  36.     pygame.quit()
  37.     i3.main_quit()
  38.     sys.exit(0)
  39.  
  40. def signal_reload(signal, frame):
  41.     read_config()
  42.  
  43. def signal_show(signal, frame):
  44.     global global_updates_running
  45.     if not global_updates_running:
  46.         global_updates_running = True
  47.     else:
  48.         root = i3.get_tree()
  49.         if root.find_focused().type != 'workspace' and root.find_focused().fullscreen_mode == 1:
  50.             print('Cannot open - fullscreen window present!')
  51.             return False
  52.         global_updates_running = False
  53.         ui_thread = Thread(target = show_ui)
  54.         ui_thread.daemon = True
  55.         ui_thread.start()
  56.  
  57. signal.signal(signal.SIGINT, signal_quit)
  58. signal.signal(signal.SIGTERM, signal_quit)
  59. signal.signal(signal.SIGHUP, signal_reload)
  60. signal.signal(signal.SIGUSR1, signal_show)
  61.  
  62. config = configparser.RawConfigParser()
  63.  
  64. def get_color(section = None, option = None, raw = None):
  65.     if not raw:
  66.         raw = config.get(section, option)
  67.  
  68.     try:
  69.         return pygame.Color(*raw)
  70.     except (ValueError, TypeError):
  71.         pass
  72.  
  73.     try:
  74.         return pygame.Color(raw)
  75.     except ValueError:
  76.         pass
  77.  
  78.     if raw[0] == '#' and len(raw[1:]) == 3:
  79.         try:
  80.             r = int(raw[1], 16)
  81.             g = int(raw[2], 16)
  82.             b = int(raw[3], 16)
  83.             return pygame.Color(r * 16, g * 16, b * 16, 255)
  84.         except ValueError:
  85.             pass
  86.  
  87.     if raw[0] == '#' and len(raw[1:]) == 6:
  88.         try:
  89.             r = int(raw[1:2], 16)
  90.             g = int(raw[3:4], 16)
  91.             b = int(raw[5:6], 16)
  92.             return pygame.Color(r, g, b, 255)
  93.         except ValueError:
  94.             pass
  95.  
  96.     raise ValueError
  97.  
  98.     #except Exception as e:
  99.     #    print traceback.format_exc()
  100.  
  101. defaults = {
  102.         ('Capture', 'screenshot_width'): (config.getint, pygame.display.Info().current_w),
  103.         ('Capture', 'screenshot_height'): (config.getint, pygame.display.Info().current_h),
  104.         ('Capture', 'screenshot_offset_x'): (config.getint, 0),
  105.         ('Capture', 'screenshot_offset_y'): (config.getint, 0),
  106.  
  107.         ('UI', 'window_width'): (config.getint, pygame.display.Info().current_w),
  108.         ('UI', 'window_height'): (config.getint, pygame.display.Info().current_h),
  109.         ('UI', 'bgcolor'): (get_color, get_color(raw = 'gray20')),
  110.         ('UI', 'workspaces'): (config.getint, None),
  111.         ('UI', 'grid_x'): (config.getint, None),
  112.         ('UI', 'grid_y'): (config.getint, None),
  113.         ('UI', 'padding_percent_x'): (config.getint, 5),
  114.         ('UI', 'padding_percent_y'): (config.getint, 5),
  115.         ('UI', 'spacing_percent_x'): (config.getint, 5),
  116.         ('UI', 'spacing_percent_y'): (config.getint, 5),
  117.         ('UI', 'frame_width_px'): (config.getint, 5),
  118.         ('UI', 'frame_active_color'): (get_color, get_color(raw = '#3b4f8a')),
  119.         ('UI', 'frame_inactive_color'): (get_color, get_color(raw = '#43747b')),
  120.         ('UI', 'frame_unknown_color'): (get_color, get_color(raw = '#c8986b')),
  121.         ('UI', 'frame_empty_color'): (get_color, get_color(raw = 'gray60')),
  122.         ('UI', 'frame_nonexistant_color'): (get_color, get_color(raw = 'gray30')),
  123.         ('UI', 'tile_active_color'): (get_color, get_color(raw = '#5a6da4')),
  124.         ('UI', 'tile_inactive_color'): (get_color, get_color(raw = '#93afb3')),
  125.         ('UI', 'tile_unknown_color'): (get_color, get_color(raw = '#ffe6d0')),
  126.         ('UI', 'tile_empty_color'): (get_color, get_color(raw = 'gray80')),
  127.         ('UI', 'tile_nonexistant_color'): (get_color, get_color(raw = 'gray40')),
  128.         ('UI', 'names_show'): (config.getboolean, 'True'),
  129.         ('UI', 'names_font'): (config.get, 'sans-serif'),
  130.         ('UI', 'names_fontsize'): (config.getint, 25),
  131.         ('UI', 'names_color'): (get_color, get_color(raw = 'white')),
  132.         ('UI', 'thumb_stretch'): (config.getboolean, 'False'),
  133.         ('UI', 'highlight_percentage'): (config.getint, 20),
  134.         ('UI', 'switch_to_empty_workspaces'): (config.getboolean, 'False')
  135. }
  136.  
  137. def read_config():
  138.     config.read(os.path.join(xdg_config_home, "i3expo", "config"))
  139.     for option in defaults.keys():
  140.         if not isset(option):
  141.             if defaults[option][1] == None:
  142.                 print("Error: Mandatory option " + str(option) + " not set!")
  143.                 sys.exit(1)
  144.             config.set(*option, value=defaults[option][1])
  145.  
  146. def get_config(*option):
  147.     return defaults[option][0](*option)
  148.  
  149. def isset(option):
  150.     try:
  151.         if defaults[option][0](*option) == "None":
  152.             return False
  153.         return True
  154.     except ValueError:
  155.         return False
  156.  
  157. def grab_screen():
  158.     x1 = get_config('Capture','screenshot_offset_x')
  159.     y1 = get_config('Capture','screenshot_offset_y')
  160.     x2 = get_config('Capture','screenshot_width')
  161.     y2 = get_config('Capture','screenshot_height')
  162.     w, h = x2-x1, y2-y1
  163.     size = w * h
  164.     objlength = size * 3
  165.  
  166.     grab.getScreen.argtypes = []
  167.     result = (ctypes.c_ubyte*objlength)()
  168.  
  169.     grab.getScreen(x1,y1, w, h, result)
  170.     pil = Image.frombuffer('RGB', (w, h), result, 'raw', 'RGB', 0, 1)
  171.     #draw = ImageDraw.Draw(pil)
  172.     #draw.text((100,100), 'abcde')
  173.     return pygame.image.fromstring(pil.tobytes(), pil.size, pil.mode)
  174.  
  175. def update_workspace(workspace):
  176.     if workspace.num not in global_knowledge.keys():
  177.         global_knowledge[workspace.num] = {
  178.                 'name': None,
  179.                 'screenshot': None,
  180.                 'windows': {}
  181.         }
  182.  
  183.     global_knowledge[workspace.num]['name'] = workspace.name
  184.  
  185.     global_knowledge['active'] = workspace.num
  186.  
  187. def init_knowledge():
  188.     root = i3.get_tree()
  189.     for workspace in root.workspaces():
  190.         update_workspace(workspace)
  191.  
  192. def update_state(i3, e):
  193.     if not global_updates_running:
  194.         return False
  195.  
  196.     current_workspace = i3.get_tree().find_focused().workspace()
  197.     update_workspace(current_workspace)
  198.     global_knowledge[current_workspace.num]['screenshot'] = grab_screen()
  199.  
  200.     root = i3.get_tree()
  201.     deleted = []
  202.     for num in global_knowledge.keys():
  203.         if type(num) is int and num not in [w.num for w in root.workspaces()]:
  204.             deleted += [num]
  205.     for num in deleted:
  206.         del(global_knowledge[num])
  207.  
  208. def get_hovered_frame(mpos, frames):
  209.     for frame in frames.keys():
  210.         if mpos[0] > frames[frame]['ul'][0] \
  211.                 and mpos[0] < frames[frame]['br'][0] \
  212.                 and mpos[1] > frames[frame]['ul'][1] \
  213.                 and mpos[1] < frames[frame]['br'][1]:
  214.             return frame
  215.     return None
  216.  
  217. def show_ui():
  218.     global global_updates_running
  219.  
  220.     window_width = get_config('UI', 'window_width')
  221.     window_height = get_config('UI', 'window_height')
  222.    
  223.     workspaces = get_config('UI', 'workspaces')
  224.     grid_x = get_config('UI', 'grid_x')
  225.     grid_y = get_config('UI', 'grid_y')
  226.    
  227.     padding_x = get_config('UI', 'padding_percent_x')
  228.     padding_y = get_config('UI', 'padding_percent_y')
  229.     spacing_x = get_config('UI', 'spacing_percent_x')
  230.     spacing_y = get_config('UI', 'spacing_percent_y')
  231.     frame_width = get_config('UI', 'frame_width_px')
  232.    
  233.     frame_active_color = get_config('UI', 'frame_active_color')
  234.     frame_inactive_color = get_config('UI', 'frame_inactive_color')
  235.     frame_unknown_color = get_config('UI', 'frame_unknown_color')
  236.     frame_empty_color = get_config('UI', 'frame_empty_color')
  237.     frame_nonexistant_color = get_config('UI', 'frame_nonexistant_color')
  238.    
  239.     tile_active_color = get_config('UI', 'tile_active_color')
  240.     tile_inactive_color = get_config('UI', 'tile_inactive_color')
  241.     tile_unknown_color = get_config('UI', 'tile_unknown_color')
  242.     tile_empty_color = get_config('UI', 'tile_empty_color')
  243.     tile_nonexistant_color = get_config('UI', 'tile_nonexistant_color')
  244.    
  245.     names_show = get_config('UI', 'names_show')
  246.     names_font = get_config('UI', 'names_font')
  247.     names_fontsize = get_config('UI', 'names_fontsize')
  248.     names_color = get_config('UI', 'names_color')
  249.  
  250.     thumb_stretch = get_config('UI', 'thumb_stretch')
  251.     highlight_percentage = get_config('UI', 'highlight_percentage')
  252.  
  253.     switch_to_empty_workspaces = get_config('UI', 'switch_to_empty_workspaces')
  254.  
  255.     screen = pygame.display.set_mode((window_width, window_height), pygame.RESIZABLE)
  256.     pygame.display.set_caption('i3expo')
  257.  
  258.     total_x = screen.get_width()
  259.     total_y = screen.get_height()
  260.  
  261.     pad_x = round(total_x * padding_x / 100)
  262.     pad_y = round(total_y * padding_y / 100)
  263.  
  264.     space_x = round(total_x * spacing_x / 100)
  265.     space_y = round(total_y * spacing_y / 100)
  266.  
  267.     shot_outer_x = round((total_x - 2 * pad_x - space_x * (grid_x - 1)) / grid_x)
  268.     shot_outer_y = round((total_y - 2 * pad_y - space_y * (grid_y - 1)) / grid_y)
  269.  
  270.     shot_inner_x = shot_outer_x - 2 * frame_width
  271.     shot_inner_y = shot_outer_y - 2 * frame_width
  272.  
  273.     offset_delta_x = shot_outer_x + space_x
  274.     offset_delta_y = shot_outer_y + space_y
  275.  
  276.     screen.fill(get_config('UI', 'bgcolor'))
  277.    
  278.     missing = pygame.Surface((150,200), pygame.SRCALPHA, 32)
  279.     missing = missing.convert_alpha()
  280.     qm = pygame.font.SysFont('sans-serif', 150).render('?', True, (150, 150, 150))
  281.     qm_size = qm.get_rect().size
  282.     origin_x = round((150 - qm_size[0])/2)
  283.     origin_y = round((200 - qm_size[1])/2)
  284.     missing.blit(qm, (origin_x, origin_y))
  285.  
  286.     frames = {}
  287.  
  288.     font = pygame.font.SysFont(names_font, names_fontsize)
  289.  
  290.     for y in range(grid_y):
  291.         for x in range(grid_x):
  292.  
  293.             index = y * grid_x + x + 1
  294.  
  295.             frames[index] = {
  296.                     'active': False,
  297.                     'mouseoff': None,
  298.                     'mouseon': None,
  299.                     'ul': (None, None),
  300.                     'br': (None, None)
  301.             }
  302.  
  303.             if global_knowledge['active'] == index:
  304.                 tile_color = tile_active_color
  305.                 frame_color = frame_active_color
  306.                 image = global_knowledge[index]['screenshot']
  307.             elif index in global_knowledge.keys() and global_knowledge[index]['screenshot']:
  308.                 tile_color = tile_inactive_color
  309.                 frame_color = frame_inactive_color
  310.                 image = global_knowledge[index]['screenshot']
  311.             elif index in global_knowledge.keys():
  312.                 tile_color = tile_unknown_color
  313.                 frame_color = frame_unknown_color
  314.                 image = missing
  315.             elif index <= workspaces:
  316.                 tile_color = tile_empty_color
  317.                 frame_color = frame_empty_color
  318.                 image = None
  319.             else:
  320.                 tile_color = tile_nonexistant_color
  321.                 frame_color = frame_nonexistant_color
  322.                 image = None
  323.  
  324.             origin_x = pad_x + offset_delta_x * x
  325.             origin_y = pad_y + offset_delta_y * y
  326.  
  327.             frames[index]['ul'] = (origin_x, origin_y)
  328.             frames[index]['br'] = (origin_x + shot_outer_x, origin_y + shot_outer_y)
  329.  
  330.             screen.fill(frame_color,
  331.                     (
  332.                         origin_x,
  333.                         origin_y,
  334.                         shot_outer_x,
  335.                         shot_outer_y,
  336.                     ))
  337.  
  338.             screen.fill(tile_color,
  339.                     (
  340.                         origin_x + frame_width,
  341.                         origin_y + frame_width,
  342.                         shot_inner_x,
  343.                         shot_inner_y,
  344.                     ))
  345.  
  346.             if image:
  347.                 if thumb_stretch:
  348.                     image = pygame.transform.smoothscale(image, (shot_inner_x, shot_inner_y))
  349.                     offset_x = 0
  350.                     offset_y = 0
  351.                 else:
  352.                     image_size = image.get_rect().size
  353.                     image_x = image_size[0]
  354.                     image_y = image_size[1]
  355.                     ratio_x = shot_inner_x / image_x
  356.                     ratio_y = shot_inner_y / image_y
  357.                     if ratio_x < ratio_y:
  358.                         result_x = shot_inner_x
  359.                         result_y = round(ratio_x * image_y)
  360.                         offset_x = 0
  361.                         offset_y = round((shot_inner_y - result_y) / 2)
  362.                     else:
  363.                         result_x = round(ratio_y * image_x)
  364.                         result_y = shot_inner_y
  365.                         offset_x = round((shot_inner_x - result_x) / 2)
  366.                         offset_y = 0
  367.                     image = pygame.transform.smoothscale(image, (result_x, result_y))
  368.                 screen.blit(image, (origin_x + frame_width + offset_x, origin_y + frame_width + offset_y))
  369.  
  370.             mouseoff = screen.subsurface((origin_x, origin_y, shot_outer_x, shot_outer_y)).copy()
  371.             lightmask = pygame.Surface((shot_outer_x, shot_outer_y), pygame.SRCALPHA, 32)
  372.             lightmask.convert_alpha()
  373.             lightmask.fill((255,255,255,255 * highlight_percentage / 100))
  374.             mouseon = mouseoff.copy()
  375.             mouseon.blit(lightmask, (0, 0))
  376.  
  377.             frames[index]['mouseon'] = mouseon.copy()
  378.             frames[index]['mouseoff'] = mouseoff.copy()
  379.  
  380.             defined_name = False
  381.             try:
  382.                 defined_name = config.get('Workspaces', 'workspace_' + str(index))
  383.             except:
  384.                 pass
  385.  
  386.             if names_show and (index in global_knowledge.keys() or defined_name):
  387.                 if not defined_name:
  388.                     name = global_knowledge[index]['name']
  389.                 else:
  390.                     name = defined_name
  391.                 name = font.render(name, True, names_color)
  392.                 name_width = name.get_rect().size[0]
  393.                 name_x = origin_x + round((shot_outer_x - name_width) / 2)
  394.                 name_y = origin_y + shot_outer_y + round(shot_outer_y * 0.02)
  395.                 screen.blit(name, (name_x, name_y))
  396.  
  397.     pygame.display.flip()
  398.  
  399.     running = True
  400.     use_mouse = True
  401.     while running and not global_updates_running and pygame.display.get_init():
  402.         jump = False
  403.         kbdmove = (0, 0)
  404.         for event in pygame.event.get():
  405.             if event.type == pygame.QUIT:
  406.                 running = False
  407.             elif event.type == pygame.MOUSEMOTION:
  408.                 use_mouse = True
  409.             elif event.type == pygame.KEYDOWN:
  410.                 use_mouse = False
  411.  
  412.                 if event.key == pygame.K_UP or event.key == pygame.K_k:
  413.                     kbdmove = (0, -1)
  414.                 if event.key == pygame.K_DOWN or event.key == pygame.K_j:
  415.                     kbdmove = (0, 1)
  416.                 if event.key == pygame.K_LEFT or event.key == pygame.K_h:
  417.                     kbdmove = (-1, 0)
  418.                 if event.key == pygame.K_RIGHT or event.key == pygame.K_l:
  419.                     kbdmove = (1, 0)
  420.                 if event.key == pygame.K_RETURN:
  421.                     jump = True
  422.                 if event.key == pygame.K_ESCAPE:
  423.                     running = False
  424.                 pygame.event.clear()
  425.                 break
  426.  
  427.             elif event.type == pygame.MOUSEBUTTONDOWN:
  428.                 use_mouse = True
  429.                 jump = True
  430.                 pygame.event.clear()
  431.                 break
  432.  
  433.         if use_mouse:
  434.             mpos = pygame.mouse.get_pos()
  435.             active_frame = get_hovered_frame(mpos, frames)
  436.         elif kbdmove != (0, 0):
  437.             if active_frame == None:
  438.                 active_frame = 1
  439.             if kbdmove[0] != 0:
  440.                 active_frame += kbdmove[0]
  441.             elif kbdmove[1] != 0:
  442.                 active_frame += kbdmove[1] * grid_x
  443.             if active_frame > workspaces:
  444.                 active_frame -= workspaces
  445.             elif active_frame < 0:
  446.                 active_frame += workspaces
  447.             print(active_frame)
  448.  
  449.         if jump:
  450.             if active_frame in global_knowledge.keys():
  451.                 i3.command('workspace ' + str(global_knowledge[active_frame]['name']))
  452.                 break
  453.             if switch_to_empty_workspaces:
  454.                 defined_name = False
  455.                 try:
  456.                     defined_name = config.get('Workspaces', 'workspace_' + str(active_frame))
  457.                 except:
  458.                     pass
  459.                 if defined_name:
  460.                     i3.command('workspace ' + defined_name)
  461.                     break
  462.  
  463.         for frame in frames.keys():
  464.             if frames[frame]['active'] and not frame == active_frame:
  465.                 screen.blit(frames[frame]['mouseoff'], frames[frame]['ul'])
  466.                 frames[frame]['active'] = False
  467.         if active_frame and not frames[active_frame]['active']:
  468.             screen.blit(frames[active_frame]['mouseon'], frames[active_frame]['ul'])
  469.             frames[active_frame]['active'] = True
  470.  
  471.         pygame.display.update()
  472.         pygame.time.wait(25)
  473.  
  474.     pygame.display.quit()
  475.     pygame.display.init()
  476.     global_updates_running = True
  477.  
  478. if __name__ == '__main__':
  479.  
  480.     read_config()
  481.     init_knowledge()
  482.     update_state(i3, None)
  483.  
  484.     i3.on('window', update_state)
  485.     i3.on('workspace', update_state)
  486.  
  487.     i3_thread = Thread(target = i3.main)
  488.     i3_thread.daemon = True
  489.     i3_thread.start()
  490.  
  491.     while True:
  492.         time.sleep(5)
  493.         update_state(i3, None)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement