#!/usr/bin/env python import gi import math import cairo import subprocess as sp import sys import os from os.path import expanduser import toml gi.require_version("Gtk","3.0") gi.require_version("Gdk","3.0") gi.require_version("GtkLayerShell","0.1") from gi.repository import Gtk from gi.repository import Gdk from gi.repository import GtkLayerShell Gtk.init() def point_on_circle(r, point, angle): x = point[0] + r * math.cos(angle) y = point[1] + r * math.sin(angle) return [x,y] def gradient(x1,y1,x2,y2): return y1-y2/x1-x2 class MenuWindow(Gtk.Window): def __init__(self, options, colors,config): super().__init__() self.set_title("sike") self.options = options self.all_options = options self.path = [] self.colors = colors self.config = config self.selected = None self.set_resizable(False) self.set_decorated(False) self.mouse_x = 0 self.mouse_y = 0 self.connect("destroy",Gtk.main_quit) self.area = Gtk.DrawingArea() self.area.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self.area.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self.area.add_events(Gdk.EventMask.BUTTON_MOTION_MASK) self.area.add_events(Gdk.EventMask.TOUCH_MASK) self.area.connect("draw",self.draw_area) self.area.connect("button_release_event",self.on_selection) self.area.connect("motion_notify_event",self.on_move) #self.area.connect("touch_event",self.on_touch) self.add(self.area) def scale(self,width,height): self.width = width/3 self.height = height self.set_default_size(width/3,height) self.move(width-self.width,0) def on_selection(self,widget,data): if self.selected != None: self.path.append(self.selected) opt = self.options[self.selected] if opt["type"] == "shell": sp.Popen(opt["command"].split(" ")) self.destroy() elif opt["type"] == "internal": if opt["command"] == "exit": self.destroy() elif opt["type"] == "i3": if type(i3) != None: i3.command(f"[id={focused.window}] focus") i3.command(opt["command"]) self.destroy() elif opt["type"] == "submenu": self.options = opt["options"] self.selected = None self.area.queue_draw() else: if self.path == []: self.destroy() else: o = self.all_options if len(self.path) == 1: self.options = o self.area.queue_draw() else: r = self.path[:-1].copy() r.reverse() for ind in r: o = o[ind] if type(o) == list: self.options = o else: self.options = o["options"] self.area.queue_draw() del self.path[0] def on_move(self,widget,data): self.area.queue_draw() self.mouse_x = data.x self.mouse_y = data.y mouse_angle = -(math.atan2(self.mouse_x-self.center[0],self.mouse_y-self.center[1]) + (math.pi/2)) # Get the angle from the center point of the menu to the mouse current = 0 if self.mouse_x > self.width-(self.width/8): self.selected = None else: for number, angle in enumerate(self.lines): if mouse_angle < angle: break else: current = number self.selected = current def draw_option(self, cr, i, option): if self.selected != None: if i == self.selected: s = self.colors["selected"] cr.set_source_rgb(s[0],s[1],s[2]) else: s = self.colors["deselected"] cr.set_source_rgb(s[0],s[1],s[2]) extents = cr.text_extents(option["label"]) cr.move_to(-self.width,0) cr.show_text(option["label"]) cr.move_to(-self.width+extents.width,-extents.height/4) cr.line_to(-self.width/8,0) cr.stroke() def draw_area(self, widget, cr): self.lines = [] width = widget.get_allocated_width() height = widget.get_allocated_height() bg = self.colors["background"] cr.set_source_rgb(bg[0],bg[1],bg[2]) cr.rectangle(0,0,width,height) cr.fill() # Draw background cr.translate(width,height/2) # All coordinates are relative to right center cp = cr.get_current_point() self.center = cr.user_to_device(cp[0],cp[1]) cr.select_font_face(self.config["font"],cairo.FontSlant.NORMAL,cairo.FontWeight.BOLD) cr.set_line_width(4) cr.set_font_size(20) start_angle = (-math.pi/12)*(len(self.options)/2) cr.rotate(start_angle) angle = start_angle cr.set_source_rgb(1,1,1) for i, o in enumerate(self.options): self.draw_option(cr, i, o) cr.rotate(math.pi/12) self.lines.append(angle) angle += math.pi/12 config_home = os.getenv("XDG_CONFIG_HOME", expanduser("~/.config")) config_file = toml.load(open(config_home + "/sike/config.toml")) if config_file["config"]["use_i3"] == True: from i3ipc import Connection i3 = Connection() focused = i3.get_tree().find_focused() else: i3 = None win = MenuWindow(config_file["options"],config_file["colors"],config_file["config"]) screen = win.get_screen() disp = screen.get_display() monitor_count = disp.get_n_monitors() if monitor_count == 1: mon = disp.get_monitor(0) else: mon = disp.get_default_monitor() g = mon.get_geometry() win.scale(g.width,g.height) GtkLayerShell.init_for_window(win) GtkLayerShell.set_layer(win, GtkLayerShell.Layer.OVERLAY) GtkLayerShell.set_anchor(win, GtkLayerShell.Edge.RIGHT, 1) GtkLayerShell.set_anchor(win, GtkLayerShell.Edge.BOTTOM, 1) GtkLayerShell.set_anchor(win, GtkLayerShell.Edge.TOP, 1) win.show_all() Gtk.main()