From ce2e0d40fec9f638696721831dd61ea5ec579ec5 Mon Sep 17 00:00:00 2001 From: kozec Date: Sat, 3 Jun 2017 12:08:42 +0200 Subject: [PATCH] binding display for OSD, 90% done --- generate_svg.py | 12 ++- images/binding-display.svg | 32 ++++--- scc/gui/svg_widget.py | 4 +- scc/osd/__init__.py | 37 +++++--- scc/osd/binding_display.py | 188 +++++++++++++++++++++++++++++++++++++ 5 files changed, 239 insertions(+), 34 deletions(-) create mode 100644 scc/osd/binding_display.py diff --git a/generate_svg.py b/generate_svg.py index 18ec197f1..d02ff964a 100755 --- a/generate_svg.py +++ b/generate_svg.py @@ -165,7 +165,7 @@ def calculate(self, gen): def place(self, gen, root): e = SVGEditor.add_element(root, "rect", - style = "opacity:1;fill-opacity:1.0;stroke-width:2.0;", + style = "opacity:1;fill-opacity:0.0;stroke-width:2.0;", fill="#000000", stroke="#06a400", id = "box_%s" % (self.name,), @@ -259,8 +259,9 @@ def __init__(self): boxes.append(box_bcs) - box_left = Box(self.PADDING, self.PADDING, Align.LEFT | Align.TOP, - "left", min_height = self.full_height * 0.5) + box_left = Box(self.PADDING, self.PADDING, Align.LEFT | Align.TOP, "left", + min_height = self.full_height * 0.5, + min_width = self.full_width * 0.2) box_left.add("LEFT", Action.AC_TRIGGER, profile.triggers.get(profile.LEFT)) box_left.add("LB", Action.AC_BUTTON, profile.buttons.get(SCButtons.LB)) box_left.add("LGRIP", Action.AC_BUTTON, profile.buttons.get(SCButtons.LGRIP)) @@ -268,8 +269,9 @@ def __init__(self): boxes.append(box_left) - box_right = Box(self.PADDING, self.PADDING, Align.RIGHT | Align.TOP, - "right", min_height = self.full_height * 0.5) + box_right = Box(self.PADDING, self.PADDING, Align.RIGHT | Align.TOP, "right", + min_height = self.full_height * 0.5, + min_width = self.full_width * 0.2) box_right.add("RIGHT", Action.AC_TRIGGER, profile.triggers.get(profile.RIGHT)) box_right.add("RB", Action.AC_BUTTON, profile.buttons.get(SCButtons.RB)) box_right.add("RGRIP", Action.AC_BUTTON, profile.buttons.get(SCButtons.RGRIP)) diff --git a/images/binding-display.svg b/images/binding-display.svg index 5e4f0be5f..3024fff50 100644 --- a/images/binding-display.svg +++ b/images/binding-display.svg @@ -68,20 +68,21 @@ - - - - + + + + - + diff --git a/scc/gui/svg_widget.py b/scc/gui/svg_widget.py index e2c45e500..e2a80129d 100644 --- a/scc/gui/svg_widget.py +++ b/scc/gui/svg_widget.py @@ -41,6 +41,7 @@ def __init__(self, app, filename, init_hilighted=True): self.current_svg = open(filename, "r").read().decode("utf-8") self.image_width = 1 + self.image_height = 1 self.image = Gtk.Image() self.parse_image() if init_hilighted: @@ -58,7 +59,8 @@ def parse_image(self): """ tree = ET.fromstring(self.current_svg.encode("utf-8")) SVGWidget.find_areas(tree, (0, 0), self.areas) - self.image_width = float(tree.attrib["width"]) + self.image_width = float(tree.attrib["width"]) + self.image_height = float(tree.attrib["height"]) def on_mouse_click(self, trash, event): diff --git a/scc/osd/__init__.py b/scc/osd/__init__.py index 48aa5d72e..a557d62a0 100644 --- a/scc/osd/__init__.py +++ b/scc/osd/__init__.py @@ -255,24 +255,35 @@ def make_window_clicktrough(self): X.destroy_region (dpy, reg) - def compute_position(self): - """ Adjusts position for currently active screen (display) """ - x, y = self.position + def get_active_screen_geometry(self): + """ + Returns geometry of active screen or None if active screen + cannot be determined. + """ screen = self.get_window().get_screen() active_window = screen.get_active_window() - width, height = self.get_window_size() if active_window: monitor = screen.get_monitor_at_window(active_window) if monitor is not None: - geometry = screen.get_monitor_geometry(monitor) - if x < 0: - x = x + geometry.x + geometry.width - width - else: - x = x + geometry.x - if y < 0: - y = y + geometry.y + geometry.height - height - else: - y = geometry.y + y + return screen.get_monitor_geometry(monitor) + return None + + + def compute_position(self): + """ Adjusts position for currently active screen (display) """ + x, y = self.position + width, height = self.get_window_size() + geometry = self.get_active_screen_geometry() + if geometry: + if x < 0: + x = x + geometry.x + geometry.width - width + else: + x = x + geometry.x + if y < 0: + y = y + geometry.y + geometry.height - height + else: + y = geometry.y + y + return x, y diff --git a/scc/osd/binding_display.py b/scc/osd/binding_display.py new file mode 100644 index 000000000..8cea93260 --- /dev/null +++ b/scc/osd/binding_display.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python2 +""" +SC-Controller - OSD Launcher + +Display launcher with phone-like keyboard that user can use to select +application (list is generated using xdg) and start it. + +Reuses styles from OSD Menu and OSD Dialog +""" +from __future__ import unicode_literals +from scc.tools import _, set_logging_level + +from gi.repository import Gtk, Gio, Gdk, GdkX11, GdkPixbuf, Pango +from scc.constants import STICK_PAD_MIN, STICK_PAD_MAX, SCButtons +from scc.constants import LEFT, RIGHT, SAME, STICK +from scc.paths import get_share_path, get_config_path +from scc.menu_data import MenuData, MenuItem +from scc.tools import point_in_gtkrect +from scc.lib import xwrappers as X +from scc.config import Config +from scc.gui.svg_widget import SVGWidget, SVGEditor +from scc.gui.daemon_manager import DaemonManager +from scc.osd.timermanager import TimerManager +from scc.osd import OSDWindow +import os, sys, re, logging +log = logging.getLogger("osd.binds") + + +class BindingDisplay(OSDWindow, TimerManager): + + def __init__(self, config=None): + self.bdisplay = os.path.join(get_config_path(), 'binding-display.svg') + if not os.path.exists(self.bdisplay): + # Prefer image in ~/.config/scc, but load default one as fallback + self.bdisplay = os.path.join(get_share_path(), "images", 'binding-display.svg') + + OSDWindow.__init__(self, "osd-keyboard") + TimerManager.__init__(self) + self.daemon = None + self.config = config or Config() + self.group = None + self.limits = {} + self.background = None + + self._eh_ids = [] + self._stick = 0, 0 + + self.c = Gtk.Box() + self.c.set_name("osd-keyboard-container") + + + def _create_background(self): + self.background = SVGWidget(self, self.args.image, init_hilighted=False) + self._pack() + + + def _pack(self): + self.c.add(self.background) + self.add(self.c) + + + def use_daemon(self, d): + """ + Allows (re)using already existing DaemonManager instance in same process + """ + self.daemon = d + self._cononect_handlers() + self.on_daemon_connected(self.daemon) + + + def _add_arguments(self): + OSDWindow._add_arguments(self) + self.argparser.add_argument('image', type=str, nargs="?", + default = self.bdisplay, help="keyboard image to use") + + + def compute_position(self): + """ + Unlike other OSD windows, this one is scaled to 80% of screen size + and centered in on active screen. + """ + x, y = 10, 10 + iw, ih = self.background.image_width, self.background.image_height + geometry = self.get_active_screen_geometry() + if geometry: + width = geometry.width * 0.8 + height = int(float(ih) / float(iw) * float(width)) + self.background.set_size_request(width, height) + x = geometry.x + ((geometry.width - width) / 2) + y = geometry.y + ((geometry.height - height) / 2) + return x, y + + + def recolor(self): + #iw, ih = self.background.image_width, self.background.image_height + #geometry = self.get_active_screen_geometry() + #if geometry: + # scale = (geometry.width * 0.8) / float(iw) + # editor = self.background.edit() + # SVGEditor.scale(SVGEditor.get_element(editor, "image"), scale) + # editor.commit() + self.background.edit().commit() + self.move(*self.compute_position()) + + + def parse_argumets(self, argv): + if not OSDWindow.parse_argumets(self, argv): + return False + return True + + + def _cononect_handlers(self): + self._eh_ids += [ + ( self.daemon, self.daemon.connect('dead', self.on_daemon_died) ), + ( self.daemon, self.daemon.connect('error', self.on_daemon_died) ), + ( self.daemon, self.daemon.connect('alive', self.on_daemon_connected) ), + ] + + + def run(self): + self.daemon = DaemonManager() + self._cononect_handlers() + OSDWindow.run(self) + + + def on_daemon_died(self, *a): + log.error("Daemon died") + self.quit(2) + + + def on_failed_to_lock(self, error): + log.error("Failed to lock input: %s", error) + self.quit(3) + + + def on_daemon_connected(self, *a): + def success(*a): + log.info("Sucessfully locked input") + pass + + c = self.choose_controller(self.daemon) + if c is None or not c.is_connected(): + # There is no controller connected to daemon + self.on_failed_to_lock("Controller not connected") + return + + self._eh_ids += [ (c, c.connect('event', self.on_event)) ] + # Lock everything + locks = [ LEFT, RIGHT, STICK, "STICKPRESS" ] + [ b.name for b in SCButtons ] + c.lock(success, self.on_failed_to_lock, *locks) + + + def quit(self, code=-1): + if self.get_controller(): + self.get_controller().unlock_all() + for source, eid in self._eh_ids: + source.disconnect(eid) + self._eh_ids = [] + OSDWindow.quit(self, code) + + + def show(self, *a): + if self.background is None: + self._create_background() + OSDWindow.show(self, *a) + self.timer('recolor', 0.1, self.recolor) + + + def on_event(self, daemon, what, data): + """ + Called when button press, button release or stick / pad update is + send by daemon. + """ + pass + + +def main(): + m = BindingDisplay() + if not m.parse_argumets(sys.argv): + sys.exit(1) + m.run() + sys.exit(m.get_exit_code()) + + +if __name__ == "__main__": + from scc.tools import init_logging + init_logging() + main()