Ejemplo n.º 1
0
def simple_start_menu():
    #https://github.com/m-col/qtile-config/blob/master/notification.py
    #TODO move definitions outside of this function so that they are only called once
    #i.e. popup = should be outside but popup.place should remain
    try:
        #log_test(1)
        popup = Popup(qtile, background="#002200", x=0, y=24, width=100, height=50, font_size=10, border="#ff00ff", border_width=1,
                    foreground="#ffffff", opacity=0.9)

        popup.place()
        popup.unhide()
        popup.text = "Reboot    Shutdown"
        popup.draw_text(x=2, y=popup.height-popup.font_size-1)  # TODO don't hard code this
        #log_test(2)
        popup.draw()
        #log_test(3)
        #popup.win.window.set_property("_NET_WM_STATE", "Above")
        popup.win.window.set_property("_NET_WM_NAME", "simple start menu")
        popup.win.update_name()

        try:  # TODO simplify this mess
            icon_file_list = ["/home/yobleck/.config/qtile/icons/system-reboot.svg",
                              "/home/yobleck/.config/qtile/icons/system-shutdown.svg"]
            icon_list = [images.Img.from_path(f) for f in icon_file_list]
            [i.resize(height=popup.height) for i in icon_list]
            surface_list = []
            for x in icon_list:
                s, _ = images._decode_to_image_surface(x.bytes_img, x.width, x.height)
                surface_list.append(s)
            [popup.draw_image(s, int(popup.width/len(surface_list))*i, -5) for i, s in enumerate(surface_list)]
            #log_test(4)
        except Exception as e:
            log_test(f"image_load error: {e}")

        def click(x, y, button):
            action_list = ["systemctl reboot", "systemctl poweroff"]
            try:
                if button == 1:
                    x_pos = int(x/popup.width*len(action_list))
                    #log_test("clicked on: " + action_list[x_pos])
                    qtile.cmd_spawn(action_list[x_pos])
                else:
                    popup.hide()
                    popup.kill()
            except Exception as e:
                log_test(f"click error: {e}")
        popup.win.process_button_click = click

        def leave(*args):
            try:
                #log_test(args)
                popup.hide()
                popup.kill()
            except Exception as e:
                log_test(f"leave error: {e}")
        popup.win.process_pointer_leave = leave
    except Exception as e:
        log_test(f"popup_test error: {e}")
Ejemplo n.º 2
0
class ShowGroupName:
    def __init__(self, duration=0.8, x_incr=50, fmt="{}", **popup_config):
        """Display popup on screen when switching to a group for `duration` in
        seconds. `x_incr` should be roughly the width of a character in the given
        `font` (monospace) at the specified `font_size`."""
        # HACK: changing width later doesn't paint more background
        popup_config["width"] = 1920
        self.duration, self.x_incr, self.fmt = duration, x_incr, fmt
        self.popup = Popup(qtile, **popup_config)
        self.pending_hide = None  # asyncio.Handle object from qtile.call_later.
        self.suppressed = False
        hook.subscribe.setgroup(self.show)
        hook.subscribe.current_screen_change(self.suppress_screen_change)

    def suppress_screen_change(self):
        """When the current screen changes, suppress the show() which would
        immediately follow due to the accompanying group change. Since the
        current_screen_change hook is fired before setgroup, this seems to be
        adequate."""
        self.suppressed = True

    def cancel_hide(self):
        """Cancel the deferred popup hide. Important when switching groups before
        the the duration is up on the last show."""
        if self.pending_hide is not None:
            self.pending_hide.cancel()

    def draw(self):
        self.popup.clear()
        self.popup.place()
        self.popup.unhide()
        self.popup.draw_text()
        self.popup.draw()

    def show(self):
        if not restarting:
            if self.suppressed:
                self.suppressed = False
            else:
                self.cancel_hide()
                grp = qtile.current_group
                scrn = grp.screen
                text = self.fmt.format(grp.name)
                self.popup.width = self.popup.horizontal_padding * 2 + (
                    len(text) * self.x_incr)
                self.popup.text = text
                self.popup.x = int(scrn.x +
                                   (scrn.width / 2 - self.popup.width / 2))
                self.popup.y = int(scrn.y +
                                   (scrn.height / 2 - self.popup.height / 2))
                self.draw()
                self.pending_hide = qtile.call_later(self.duration,
                                                     self.popup.hide)
Ejemplo n.º 3
0
class Confirm:
    def __init__(self, label, action, x_incr=50, **popup_config):
        """Confirmation popup."""
        # HACK: changing width later doesn't paint more background
        popup_config["width"] = 1920
        self.label, self.action, self.x_incr = label, action, x_incr
        self.popup = Popup(qtile, **popup_config)
        self.build_message(label)
        self.popup.win.handle_ButtonPress = self.handle_button_press
        self.popup.win.handle_KeyPress = self.handle_key_press

    def build_message(self, label):
        self.question = "Are you sure you want to %s?" % label
        self.instruction = "y / n"
        self.pad = " " * ((len(self.question) - len(self.instruction)) // 2)
        self.message = self.question + "\n" + self.pad + self.instruction

    def handle_button_press(self, ev):
        self.popup.win.cmd_focus()

    def handle_key_press(self, ev):
        if ev.detail == 29:  # y
            self.popup.hide()
            self.action()
        elif ev.detail == 57:  # n
            self.popup.hide()

    def draw(self):
        self.popup.clear()
        self.popup.place()
        self.popup.unhide()
        self.popup.draw_text()
        self.popup.draw()

    def show(self, qtile):
        grp = qtile.current_group
        scrn = grp.screen
        self.popup.width = self.popup.horizontal_padding * 2 + (
            len(self.question) * self.x_incr)
        self.popup.text = self.message
        self.popup.x = int(scrn.x + (scrn.width / 2 - self.popup.width / 2))
        self.popup.y = int(scrn.y + (scrn.height / 2 - self.popup.height / 2))
        self.draw()
        self.popup.win.cmd_focus()
Ejemplo n.º 4
0
class TVHWidget(base._Widget, base.MarginMixin):

    orientations = base.ORIENTATION_HORIZONTAL
    defaults = [
        ("refresh_interval", 30, "Time to update data"),
        ("startup_delay", 5, "Time before sending first web request"),
        ("host", "http://localhost:9981/api", "TVHeadend server address"),
        ("auth", None, "Auth details for accessing tvh. "
         "Can be None, tuple of (username, password)."),
        ("tvh_timeout", 5, "Seconds before timeout for timeout request"),
        ("popup_format", "{start:%a %d %b %H:%M}: {title:.40}",
         "Upcoming recording text."),
        ("popup_display_timeout", 10, "Seconds to show recordings."),
        ("warning_colour", "aaaa00", "Highlight when there is an error."),
        ("recording_colour", "bb0000", "Highlight when TVHeadend is recording")
    ]

    def __init__(self, **config):
        base._Widget.__init__(self, bar.CALCULATED, **config)
        self.add_defaults(TVHWidget.defaults)
        self.add_defaults(base.MarginMixin.defaults)
        self.data = []
        self.surfaces = {}
        self.iconsize = 0

    def _configure(self, qtile, bar):
        base._Widget._configure(self, qtile, bar)
        self.setup_images()

        if type(self.auth) == tuple:
            self.auth = HTTPBasicAuth(*self.auth)

        self.tvh = TVHJobServer(host=self.host,
                                auth=self.auth,
                                timeout=self.tvh_timeout)

        self.timeout_add(self.startup_delay, self.refresh)

    def _get_data(self, queue=None):
        try:
            data = self.tvh.get_upcoming()
        except TVHTimeOut:
            logger.warning("Couldn't connect to TVH server")
            data = []

        return data

    def _read_data(self, future):
        self.data = future.result()

        self.timeout_add(1, self.draw)
        self.timeout_add(self.refresh_interval, self.refresh)

    def setup_images(self):
        d_images = images.Loader(ICON_PATH)(*ICONS)

        for name, img in d_images.items():
            new_height = self.bar.height - 1
            img.resize(height=new_height)
            self.iconsize = img.width
            self.surfaces[name] = img.pattern

    def refresh(self):
        future = self.qtile.run_in_executor(self._get_data)
        future.add_done_callback(self._read_data)

    def set_refresh_timer(self):
        pass

    def calculate_length(self):
        return self.iconsize

    def draw_highlight(self, top=False, colour="000000"):

        self.drawer.set_source_rgb(colour)

        y = 0 if top else self.bar.height - 2

        # Draw the bar
        self.drawer.fillrect(0, y, self.width, 2, 2)

    def draw(self):
        # Remove background
        self.drawer.clear(self.background or self.bar.background)

        self.drawer.ctx.set_source(self.surfaces["icon"])
        self.drawer.ctx.paint()

        if not self.data:
            self.draw_highlight(top=True, colour=self.warning_colour)

        elif self.is_recording:
            self.draw_highlight(top=False, colour=self.recording_colour)

        self.drawer.draw(offsetx=self.offset, width=self.length)

    def button_press(self, x, y, button):
        self.show_recs()

    @property
    def is_recording(self):
        if not self.data:
            return False

        dtnow = datetime.now()

        for prog in self.data:
            if prog["start"] <= dtnow <= prog["stop"]:
                return True

        return False

    def mouse_enter(self, x, y):
        pass

    def show_recs(self):
        lines = []

        if not self.data:
            lines.append("No upcoming recordings.")

        else:
            lines.append("Upcoming recordings:")
            for rec in self.data:
                lines.append(self.popup_format.format(**rec))

        self.popup = Popup(self.qtile,
                           y=self.bar.height,
                           width=900,
                           height=900,
                           font="monospace",
                           horizontal_padding=10,
                           vertical_padding=10,
                           opacity=0.8)
        self.popup.text = "\n".join(lines)
        self.popup.height = (self.popup.layout.height +
                             (2 * self.popup.vertical_padding))
        self.popup.width = (self.popup.layout.width +
                            (2 * self.popup.horizontal_padding))
        self.popup.x = min(self.offsetx, self.bar.width - self.popup.width)
        self.popup.place()
        self.popup.draw_text()
        self.popup.unhide()
        self.popup.draw()
        self.timeout_add(self.popup_display_timeout, self.popup.kill)
Ejemplo n.º 5
0
class StravaWidget(base._Widget, base.MarginMixin):

    orientations = base.ORIENTATION_HORIZONTAL
    defaults = [
        ("font", "sans", "Default font"),
        ("fontsize", None, "Font size"),
        ("font_colour", "ffffff", "Text colour"),
        ("text", "{CA:%b} {CD:.1f}km", "Widget text"),
        ("refresh_interval", 1800, "Time to update data"),
        ("startup_delay", 10, "Time before sending first web request"),
        ("popup_display_timeout", 15, "Time to display extended info"),
        ("warning_colour", "aaaa00", "Highlight when there is an error."),
    ]

    format_map = {
        "CD": ("current", "distance"),
        "CC": ("current", "count"),
        "CT": ("current", "formatTime"),
        "CP": ("current", "formatPace"),
        "CN": ("current", "name"),
        "CA": ("current", "date"),
        "YD": ("year", "distance"),
        "YC": ("year", "count"),
        "YT": ("year", "formatTime"),
        "YP": ("year", "formatPace"),
        "YN": ("year", "name"),
        "YA": ("year", "date"),
        "AD": ("alltime", "distance"),
        "AC": ("alltime", "count"),
        "AT": ("alltime", "formatTime"),
        "AP": ("alltime", "formatPace"),
        "AN": ("alltime", "name"),
        "AA": ("alltime", "date"),
    }

    def __init__(self, **config):
        base._Widget.__init__(self, bar.CALCULATED, **config)
        self.add_defaults(StravaWidget.defaults)
        self.add_defaults(base.MarginMixin.defaults)
        self.data = None

    def _configure(self, qtile, bar):
        base._Widget._configure(self, qtile, bar)
        self.timeout_add(self.startup_delay, self.refresh)

    def _get_data(self, queue=None):
        return get_strava_data()

    def _read_data(self, future):
        results = future.result()

        if results:
            success, data = results

            self.data = data
            self.formatted_data = {}
            for k, v in self.format_map.items():
                obj = self.data
                for attr in v:
                    obj = getattr(obj, attr)

                self.formatted_data[k] = obj

            self.timeout_add(1, self.bar.draw)
        self.timeout_add(self.refresh_interval, self.refresh)

    def refresh(self):
        future = self.qtile.run_in_executor(self._get_data)
        future.add_done_callback(self._read_data)

    def set_refresh_timer(self):
        pass

    def calculate_length(self):

        total = 0

        if self.data is not None and self.text:

            text = self.formatText(self.text)

            width, _ = self.drawer.max_layout_size([text], self.font,
                                                   self.fontsize)

            total += (width + 2 * self.margin)

        total += self.height

        return total

    def draw_icon(self):
        scale = self.height / 24.0
        self.drawer.set_source_rgb("ffffff")
        self.drawer.ctx.set_line_width(2)
        self.drawer.ctx.move_to(8 * scale, 14 * scale)
        self.drawer.ctx.line_to(12 * scale, 6 * scale)
        self.drawer.ctx.line_to(16 * scale, 14 * scale)
        self.drawer.ctx.stroke()

        self.drawer.ctx.set_line_width(1)
        self.drawer.ctx.move_to(13 * scale, 14 * scale)
        self.drawer.ctx.line_to(16 * scale, 20 * scale)
        self.drawer.ctx.line_to(19 * scale, 14 * scale)
        self.drawer.ctx.stroke()

    def draw_highlight(self, top=False, colour="000000"):

        self.drawer.set_source_rgb(colour)

        y = 0 if top else self.bar.height - 2

        # Draw the bar
        self.drawer.fillrect(0, y, self.width, 2, 2)

    def draw(self):
        # Remove background
        self.drawer.clear(self.background or self.bar.background)

        x_offset = 0

        self.draw_icon()
        x_offset += self.height

        if self.data is None:
            self.draw_highlight(top=True, colour=self.warning_colour)

        else:
            text = self.formatText(self.text)

            # Create a text box
            layout = self.drawer.textlayout(text,
                                            self.font_colour,
                                            self.font,
                                            self.fontsize,
                                            None,
                                            wrap=False)

            # We want to centre this vertically
            y_offset = (self.bar.height - layout.height) / 2

            # Draw it
            layout.draw(x_offset + self.margin_x, y_offset)

        self.drawer.draw(offsetx=self.offset, width=self.length)

    def button_press(self, x, y, button):
        self.show_popup_summary()

    def mouse_enter(self, x, y):
        pass

    def formatText(self, text):
        try:
            return text.format(**self.formatted_data)
        except Exception as e:
            logger.warning(e)
            return "Error"

    def show_popup_summary(self):
        if not self.data:
            return False

        lines = []

        heading = ("{:^6} {:^20} {:^8} {:^10} {:^6}".format(
            "Date", "Title", "km", "time", "pace"))
        lines.append(heading)

        for act in self.data.current.children:
            line = ("{a.date:%d %b}: {a.name:<20.20} {a.distance:7,.1f} "
                    "{a.formatTime:>10} {a.formatPace:>6}").format(a=act)
            lines.append(line)

        sub = ("\n{a.date:%b %y}: {a.name:<20.20} {a.distance:7,.1f} "
               "{a.formatTime:>10} "
               "{a.formatPace:>6}").format(a=self.data.current)
        lines.append(sub)

        for month in self.data.previous:
            line = ("{a.groupdate:%b %y}: {a.name:<20.20} {a.distance:7,.1f} "
                    "{a.formatTime:>10} {a.formatPace:>6}").format(a=month)
            lines.append(line)

        year = ("\n{a.groupdate:%Y}  : {a.name:<20.20} {a.distance:7,.1f} "
                "{a.formatTime:>10} "
                "{a.formatPace:>6}").format(a=self.data.year)
        lines.append(year)

        alltime = ("\nTOTAL : {a.name:<20.20} {a.distance:7,.1f} "
                   "{a.formatTime:>10} "
                   "{a.formatPace:>6}").format(a=self.data.alltime)
        lines.append(alltime)

        self.popup = Popup(self.qtile,
                           y=self.bar.height,
                           width=900,
                           height=900,
                           font="monospace",
                           horizontal_padding=10,
                           vertical_padding=10,
                           opacity=0.8)
        self.popup.text = "\n".join(lines)
        self.popup.height = (self.popup.layout.height +
                             (2 * self.popup.vertical_padding))
        self.popup.width = (self.popup.layout.width +
                            (2 * self.popup.horizontal_padding))
        self.popup.x = min(self.offsetx, self.bar.width - self.popup.width)
        self.popup.place()
        self.popup.draw_text()
        self.popup.unhide()
        self.popup.draw()
        self.timeout_add(self.popup_display_timeout, self.popup.kill)
Ejemplo n.º 6
0
class REPL(base._TextBox):
    """A flexible textbox that can be updated from bound keys, scripts, and qshell."""
    defaults = [
        ("font", "sans", "Text font."),
        ("fontsize", None, "Font pixel size. Calculated if None."),
        ("fontshadow", None, "font shadow color, default is None(no shadow)."),
        ("padding", None, "Padding left and right. Calculated if None."),
        ("foreground", "#ffffff", "Foreground colour."),
        ("win_pos", (0,0), "Initial window coordinates as tuple (x, y)."),
        ("win_size", (500,500), "Window size as tuple (w, h)."),
        ("win_bordercolor", "#ff0000", "Window border color. Can be list of multiple values."),
        ("win_borderwidth", 2, "Window Border width."),
        ("win_foreground", "#ffffff", "Window text color"),
        ("win_background", "#000000", "Window background color"),
        ("win_opacity", 1, "Window opacity."),
        ("win_font", "sans", "Window text font."),
        ("win_fontsize", 12, "Window font pixel size. Calculated if None."),
        ("win_fontshadow", None, "Window font shadow color, default is None(no shadow)."),
        ("win_mousebutton_move", 3, "Mouse button that when clicked on repl window causes it to re-center under mouse."),
    ]

    def __init__(self, text="PY REPL", width=bar.CALCULATED, **config):
        base._TextBox.__init__(self, text=text, width=width, **config)
        self.add_defaults(REPL.defaults)
        self.add_callbacks({"Button1": self.open_win, "Button3":self.leave})
        self.og_bartext = self.text
        self.text = "_" + self.text
        self.win_opened = False

    def _configure(self, qtile, bar):
        base._TextBox._configure(self, qtile, bar)

        #self.popup = Popup(qtile, background="#000000", foreground="#00aa00", x=1280, y=720, width=500, height=500, font="Noto Mono",
                           #font_size=12, border=["#0000ff", "#0000ff", "#ffff00", "#ffff00"], border_width=5, opacity=0.90, wrap=True)

        self.popup = Popup(qtile, background=self.win_background, foreground=self.win_foreground,
                           x=self.win_pos[0], y=self.win_pos[1],
                           width=self.win_size[0], height=self.win_size[1],
                           font=self.win_font, font_size=self.win_fontsize, fontshadow=self.win_fontshadow,
                           border=self.win_bordercolor, border_width=self.win_borderwidth,
                           opacity=self.win_opacity, wrap=True)

        self.popup.layout.markup = False  # TODO PR to add this to popup options
        self.popup.layout.width = self.popup.width  # actually enforce line wrap. see PR above
        #self.popup.place()
        #self.popup.unhide()

        self.nl = "\n"
        self.old_text = ["Yobleck's simple python REPL (click to focus, esc to quit, F5/'cls'/'clear' to reset screen)",
                    f"Qtile: {metadata.distribution('qtile').version}",
                    f"Python: {sys.version.replace(self.nl, '')}", ">>> "]
        self.new_text = ""
        self.popup.text = lines_to_text(self.old_text) + "\u2588" #[-num_rows:]
        self.draw_y = 5
        self.popup.draw_text(x=2, y=self.draw_y)
        self.popup.draw()
        self.history = []  # command history for this popup instance. save across session with global var?
        self.history_index = -1  # is there a way to do this without the var?
        self.indentation_level = 0

        self.popup.win.process_key_press = self.key_press
        #self.popup.win.process_pointer_leave = self.leave
        self.popup.win.process_button_click = self.b_press
        self.popup.win.process_pointer_enter = self.enter

    def key_press(self, keycode):
        """Handle key presses."""
        try:
            keychr = chr(keycode)
            log_test(f"key {keycode}={keychr} pressed")  # TODO convert keycodes to text

            if keycode == 65307:  # escape
                self.leave()
                return
            elif keycode == 65474:  # F5 to clear screen
                self.old_text = [">>> "]
                self.new_text = ""
            # scrolling
            elif keycode == 65366:  # page down
                self.draw_y -= 5
            elif keycode == 65365:  # page up
                self.draw_y += 5
            elif keycode == 65288:  # backspace
                if self.new_text[-1] == "\n":
                    self.indentation_level -= 1
                self.new_text = self.new_text[:-1]
            elif keycode == 65289:  # tab. NOTE probably never going to have tab completion
                self.new_text += "\t"
            # history
            elif keycode in [65362, 65364]:  # up/down arrow keys. TODO double check with another non 60% keyboard
                if self.history:
                    if keycode == 65362 and self.history_index < len(self.history)-1:
                        self.history_index += 1
                        self.new_text = self.history[self.history_index]
                    elif keycode == 65364 and self.history_index > 0:
                        self.history_index -= 1
                        self.new_text = self.history[self.history_index]
            elif keycode == 65293:  # enter
                self.history.insert(0, self.new_text)
                self.history_index = -1
                if self.new_text:
                    if self.new_text in ["exit", "exit()", "quit", "quit()"] or "sys.exit" in self.new_text:
                        # exit commands. WARNING will eval("quit()") kill qtile? sys.exit solution will kill regardless of conditionals
                        self.leave()
                        return
                    elif self.new_text in ["clear", "cls"]:  # clear screen commands
                        self.old_text = [">>> "]
                        self.new_text = ""
                    elif self.new_text[-1] == ":":
                        self.indentation_level += 1
                        self.new_text += "\n" + "    "*indentation_level
                    else:
                        #old_text += new_text + "\n" + _simple_eval_exec(new_text) + ">>> "  # append input text, eval and append results
                        self.old_text[-1] += self.new_text
                        self.old_text.extend([ _simple_eval_exec(self.new_text), ">>> "])
                        self.new_text = ""
                        self.indentation_level = 0
            elif keycode in [65505, 65507, 65508, 65506, 65513, 65514, 65515]:  # range(65505, 65518) for mod keys?
                pass  # ignore modifiers and other non text keys. TODO modifier list that is cleared after non modifier is pressed. act on list?
            else:
                self.new_text += keychr

            # actually drawing text
            self.popup.clear()
            self.popup.text = lines_to_text(self.old_text) + self.new_text + "\u2588"  # TODO scroll to bottom after hitting enter
            self.popup.draw_text(x=2, y=self.draw_y)
            self.popup.draw()
            self.popup.text = lines_to_text(self.old_text) #[-num_rows:] [-num_rows:]
        except Exception as e:
            log_test(f"key press error: {e}")

    def leave(self, *args):  # close window when mouse leaves
        """Handle hiding repl window."""
        try:
            log_test("pointer leave")
            self.popup.hide()
            #self.popup.kill()
            self.cmd_update("_" + self.og_bartext)#"REPL Closed")
            self.win_opened = False
        except Exception as e:
            log_test(f"pointer leave error: {e}")

    def b_press(self, x, y, button):  # two functions cause mouse click doesn't work with *args
        """ Handle mouse button presses."""
        try:
            if button == 1:
                self.popup.win.focus(warp=False)
            elif button in [4,5]:
                if button == 4:  # mouse wheel up
                    self.draw_y += 5
                elif button == 5:  # down
                    self.draw_y -= 5
                self.popup.clear()  # NOTE DRY violation
                self.popup.text = lines_to_text(self.old_text) + self.new_text
                self.popup.draw_text(x=2, y=self.draw_y)
                self.popup.draw()
                self.popup.text = lines_to_text(self.old_text)
            elif button == self.win_mousebutton_move:  # move the window around (crudely)
                new_pos = (self.popup.x+x-self.popup.width//2, self.popup.y+y-self.popup.height//2)
                self.popup.win.place(new_pos[0], new_pos[1], self.popup.width, self.popup.height, self.popup.border_width, self.popup.border)
                self.popup.x, self.popup.y = new_pos  # .place() doesn't change these
            log_test(f"button {button} clicked")
        except Exception as e:
            log_test(f"button click error: {e}")

    def enter(self, *args):
        """Handle mouse enter window to focus."""
        try:
            self.popup.win.focus(warp=False)
            log_test("pointer enter")
        except Exception as e:
            log_test(f"pointer enter error: {e}")

    def open_win(self):
        """Open python repl in internal window."""
        if not self.win_opened:
            self.win_opened = True
            self.cmd_update(self.og_bartext)#"REPL Opened")
            #simple_repl()
            self.popup.place()
            self.popup.unhide()

    """def close_win(self):
        #Close python repl
        if self.win_opened:
            self.win_opened = False
            self.cmd_update("REPL Closed")
            self.leave()"""

    def cmd_update(self, text):
        """Update the text in a TextBox widget."""
        self.update(text)

    def cmd_get(self):
        """Retrieve the text in a TextBox widget."""
        return self.text 
Ejemplo n.º 7
0
def simple_repl():  # old config hack version. see updated widget version
    try:
        popup = Popup(qtile, background="#000000", foreground="#00aa00", x=1280, y=720, width=500, height=500, font="Noto Mono", font_size=12,
                      border=["#0000ff", "#0000ff", "#ffff00", "#ffff00"], border_width=5, opacity=0.90, wrap=True)

        popup.layout.markup = False  # TODO PR to add this to popup options
        popup.layout.width = popup.width  # actually enforce line wrap. see PR above
        popup.place()
        popup.unhide()

        #num_cols = popup.width//8  # 8 assumes Noto Mono font at size 12
        #num_rows = popup.height//15  # ditto
        nl = "\n"
        old_text = ["Yobleck's simple python REPL (click to focus, esc to quit, F5/'cls'/'clear' to reset screen)",
                    f"Qtile: {metadata.distribution('qtile').version}",
                    f"Python: {sys.version.replace(nl, '')}", ">>> "]
        new_text = ""
        popup.text = lines_to_text(old_text) + "\u2588" #[-num_rows:]
        draw_y = 5
        popup.draw_text(x=2, y=draw_y)
        popup.draw()
        #globals()["tert"] = "terting"
        history = []  # command history for this popup instance. save across session with global var?
        history_index = -1  # is there a way to do this without the var?
        indentation_level = 0

        def key_press(keycode):
            try:
                nonlocal new_text
                nonlocal old_text
                nonlocal draw_y
                nonlocal history_index
                nonlocal indentation_level
                keychr = chr(keycode)
                log_test(f"key {keycode}={keychr} pressed")  # TODO convert keycodes to text

                if keycode == 65307:  # escape
                    leave()
                    return
                elif keycode == 65474:  # F5 to clear screen
                    old_text = [">>> "]
                    new_text = ""
                # scrolling
                elif keycode == 65366:  # page down
                    draw_y -= 5
                elif keycode == 65365:  # page up
                    draw_y += 5
                elif keycode == 65288:  # backspace
                    if new_text[-1] == "\n":
                        indentation_level -= 1
                    new_text = new_text[:-1]
                elif keycode == 65289:  # tab. NOTE probably never going to have tab completion
                    new_text += "\t"
                # history
                elif keycode in [65362, 65364]:  # up/down arrow keys. TODO double check with another non 60% keyboard
                    if history:
                        if keycode == 65362 and history_index < len(history)-1:
                            history_index += 1
                            new_text = history[history_index]
                        elif keycode == 65364 and history_index > 0:
                            history_index -= 1
                            new_text = history[history_index]
                elif keycode == 65293:  # enter
                    history.insert(0, new_text)
                    history_index = -1
                    if new_text:
                        if new_text in ["exit", "exit()", "quit", "quit()"] or "sys.exit" in new_text:
                            # exit commands. WARNING will eval("quit()") kill qtile? sys.exit solution will kill regardless of conditionals
                            leave()
                            return
                        elif new_text in ["clear", "cls"]:  # clear screen commands
                            old_text = [">>> "]
                            new_text = ""
                        elif new_text[-1] == ":":
                            indentation_level += 1
                            new_text += "\n" + "    "*indentation_level
                        else:
                            #old_text += new_text + "\n" + _simple_eval_exec(new_text) + ">>> "  # append input text, eval and append results
                            old_text[-1] += new_text
                            old_text.extend([ _simple_eval_exec(new_text), ">>> "])
                            new_text = ""
                            indentation_level = 0
                elif keycode in [65505, 65507, 65508, 65506, 65513, 65514, 65515]:  # range(65505, 65518) for mod keys?
                    pass  # ignore modifiers and other non text keys. TODO modifier list that is cleared after non modifier is pressed. act on list?
                else:
                    new_text += keychr

                # actually drawing text
                popup.clear()
                popup.text = lines_to_text(old_text) + new_text + "\u2588"  # TODO scroll to bottom after hitting enter
                popup.draw_text(x=2, y=draw_y)
                popup.draw()
                popup.text = lines_to_text(old_text) #[-num_rows:] [-num_rows:]
            except Exception as e:
                log_test(f"key press error: {e}")
        popup.win.process_key_press = key_press

        def leave(*args):  # close window when mouse leaves
            try:
                log_test("pointer leave")
                popup.hide()
                popup.kill()
            except Exception as e:
                log_test(f"pointer leave error: {e}")
        #popup.win.process_pointer_leave = leave
        
        # focus window
        def b_press(x, y, button):  # two functions cause mouse click doesn't work with *args
            try:
                nonlocal draw_y
                if button == 1:
                    popup.win.focus(warp=False)
                elif button in [4,5]:
                    if button == 4:  # mouse wheel up
                        draw_y += 5
                    elif button == 5:  # down
                        draw_y -= 5
                    popup.clear()  # NOTE DRY violation
                    popup.text = lines_to_text(old_text) + new_text
                    popup.draw_text(x=2, y=draw_y)
                    popup.draw()
                    popup.text = lines_to_text(old_text)
                elif button == 8:  # move the window around (crudely)
                    new_pos = (popup.x+x-popup.width//2, popup.y+y-popup.height//2)
                    popup.win.place(new_pos[0], new_pos[1], popup.width, popup.height, popup.border_width, popup.border)
                    popup.x, popup.y = new_pos  # .place() doesn't change these
                log_test(f"button {button} clicked")
            except Exception as e:
                log_test(f"button click error: {e}")
        popup.win.process_button_click = b_press
        def enter(*args):
            try:
                popup.win.focus(warp=False)
                log_test("pointer enter")
            except Exception as e:
                log_test(f"pointer enter error: {e}")
        popup.win.process_pointer_enter = enter

    except Exception as e:
        log_test(f"repl error: {e}")