def configure(self): """ This method needs to be called to set up the Server with the Qtile manager and create the required popup windows. """ if self.horizontal_padding is None: self.horizontal_padding = self.font_size / 2 if self.vertical_padding is None: self.vertical_padding = self.font_size / 2 popup_config = {} for opt in Popup.defaults: key = opt[0] if hasattr(self, key): value = getattr(self, key) if isinstance(value, (tuple, list)): popup_config[key] = value[1] else: popup_config[key] = value for win in range(self.max_windows): popup = Popup(qtile, **popup_config) popup.win.handle_ButtonPress = self._buttonpress(popup) popup.replaces_id = None self._hidden.append(popup) self._positions.append( (self.x, self.y + win * (self.height + 2 * self.border_width + self.gap))) notifier.register(self._notify, Server.capabilities)
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)
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 __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 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)
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)
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 test_popup_focus(manager): manager.test_xeyes() manager.windows_map = {} # we have to add .conn so that Popup thinks this is libqtile.qtile manager.conn = xcbq.Connection(manager.display) try: popup = Popup(manager) popup.width = manager.c.screen.info()["width"] popup.height = manager.c.screen.info()["height"] popup.place() popup.unhide() assert manager.c.group.info()['focus'] == 'xeyes' assert manager.c.group.info()['windows'] == ['xeyes'] assert len(manager.c.windows()) == 1 popup.hide() finally: popup.kill() manager.conn.finalize()
def test_focus(qtile): qtile.conn = xcbq.Connection(qtile.display) qtile.test_xeyes() qtile.windows_map = {} popup = Popup(qtile) popup.width = qtile.c.screen.info()["width"] popup.height = qtile.c.screen.info()["height"] popup.place() popup.unhide() assert qtile.c.group.info()['focus'] == 'xeyes' assert qtile.c.group.info()['windows'] == ['xeyes'] assert len(qtile.c.windows()) == 1 popup.hide() popup.kill()
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)
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)
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
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}")
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}")
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()