123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- #!/usr/bin/env python3
- import os
- import sys
- import argparse
- import random
- from subprocess import Popen
- import gi
- gi.require_version("Gtk", "3.0")
- gi.require_version("PangoCairo", "1.0")
- gi.require_version("Gdk", "3.0")
- gi.require_version("GLib", "2.0")
- from gi.repository import Gtk, Gio, cairo, Pango, Gdk, PangoCairo, GLib, GObject
- def get_parser():
- p = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
- p.add_argument(
- "--read-only",
- action="store_true",
- dest="readonly",
- help="Display but dont change the number",
- )
- p.add_argument(
- "--stdin",
- action="store_true",
- default=True,
- dest="stdin",
- help="stdin is read and shown",
- )
- p.add_argument("--fullscreen", action="store_true", default=True, dest="fullscreen")
- p.add_argument(
- "--no-fullscreen", action="store_false", default=True, dest="fullscreen"
- )
- p.add_argument("--background", default="#111")
- p.add_argument("--foreground", default="#eee")
- p.add_argument(
- "--invert-every",
- type=float,
- metavar="SECONDS",
- default=60,
- help=(
- "Swap foreground and background periodically; "
- "this is useful to avoid CRT-burning; use <= 0 to disable"
- ),
- )
- p.add_argument("--on-key-press",
- type=str,
- default=None,
- help="A program to call on up/down keypress. "
- "It will be called with appropriate arguments "
- "representing the action to take.")
- return p
- class App(Gtk.Application):
- def __init__(self, cli_args, *args, **kwargs):
- super().__init__(
- *args,
- application_id="org.hackmeeting.numeretti",
- flags=Gio.ApplicationFlags.FLAGS_NONE,
- **kwargs
- )
- self.window = None
- self.cli_args = cli_args
- self._text = ""
- self._stdin_buffer = ""
- if self.cli_args.stdin:
- os.set_blocking(sys.stdin.fileno(), False)
- GLib.io_add_watch(sys.stdin.fileno(), GLib.IO_IN, self._on_stdin_data)
- def _on_stdin_data(self, *args, **kwargs):
- max_line_length = 16
- incoming_data = sys.stdin.read(max_line_length)
- if not incoming_data:
- return True
- self._stdin_buffer += incoming_data
- if len(self._stdin_buffer) == max_line_length:
- self._stdin_buffer += '\n'
- if "\n" in self._stdin_buffer:
- parts = self._stdin_buffer.split("\n")
- self._stdin_buffer = parts[-1]
- self.text = parts[-2]
- return True
- def do_startup(self):
- Gtk.Application.do_startup(self)
- action = Gio.SimpleAction.new("quit", None)
- action.connect("activate", self.on_quit)
- self.add_action(action)
- # self.set_app_menu(…)
- def do_activate(self):
- # We only allow a single window and raise any existing ones
- if not self.window:
- # Windows are associated with the application
- # when the last one is closed the application shuts down
- self.window = AppWindow(application=self, title="Main Window")
- self.window.present()
- def on_quit(self, action, param):
- sys.stdin.close()
- self.quit()
- @GObject.Property(type=str,
- default="",
- flags=GObject.ParamFlags.READWRITE)
- def text(self) -> str:
- return self._text
- @text.setter
- def text(self, new_value: str):
- self._text = new_value
- def on_binding_pressed(self, action):
- print('calling:', action)
- Popen(self.cli_args.on_key_press.split() + [action])
- def get_color(description: str) -> Gdk.RGBA:
- rgba = Gdk.RGBA()
- ret = rgba.parse(description)
- if ret is False:
- raise ValueError("Error parsing color! %s" % description)
- return rgba
- class AppWindow(Gtk.ApplicationWindow):
- def __init__(self, *args, **kwargs):
- self.app = kwargs["application"]
- super().__init__(*args, **kwargs)
- self.rotation = 0
- self.invert_colors = False
- self.drawing_area = Gtk.DrawingArea()
- self.drawing_area.set_can_focus(True)
- self.drawing_area.connect("draw", self.redraw)
- vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
- vbox.pack_start(self.drawing_area, True, True, 0)
- self.add(vbox)
- if self.app.cli_args.fullscreen:
- self.fullscreen()
- self.show_all()
- if self.app.cli_args.invert_every > 0:
- GLib.timeout_add(self.app.cli_args.invert_every * 1000, self.swap_colors)
- self.app.connect("notify::text", self.on_text_changed)
- if self.app.cli_args.on_key_press:
- self.connect("key-press-event", self.on_key_press)
- def on_key_press(self, widget, key: Gdk.EventKey):
- if key.state != 0 or key.type != Gdk.EventType.KEY_PRESS:
- return
- if key.keyval == Gdk.KEY_Up:
- self.app.on_binding_pressed('+1')
- elif key.keyval == Gdk.KEY_Down:
- self.app.on_binding_pressed('-1')
- def force_redraw(self):
- self.drawing_area.queue_draw()
- def on_text_changed(self, app, text: str):
- self.force_redraw()
- def swap_colors(self):
- self.invert_colors = not self.invert_colors
- self.force_redraw()
- return True
- @property
- def font(self):
- font = Pango.FontDescription()
- font.set_family("sans-serif")
- font.set_size(200 * Pango.SCALE)
- @property
- def colors(self):
- fg = self.app.cli_args.foreground
- bg = self.app.cli_args.background
- if self.invert_colors:
- fg, bg = bg, fg
- return (fg, bg)
- @property
- def bg(self):
- return get_color(self.colors[1])
- @property
- def fg(self):
- return get_color(self.colors[0])
- def redraw(self, widget, cr):
- # clearly stolen from screen-message. thanks!
- draw = self.drawing_area
- Gdk.cairo_set_source_rgba(cr, self.bg)
- cr.paint()
- layout = draw.create_pango_layout(self.app.text)
- layout.set_font_description(self.font)
- layout.set_alignment(Pango.Alignment.CENTER)
- w1, h1 = layout.get_pixel_size()
- if w1 and h1:
- w2 = draw.get_allocated_width()
- h2 = draw.get_allocated_height()
- if self.rotation in [0, 2]:
- rw1 = w1
- rh1 = h1
- else:
- rw1 = h1
- rh1 = w1
- s = min(w2 / rw1, h2 / rh1)
- cr.translate(w2 // 2, h2 // 2)
- cr.rotate(0)
- cr.scale(s, s)
- cr.translate(-w1 / 2, -h1 / 2)
- Gdk.cairo_set_source_rgba(cr, self.fg)
- PangoCairo.show_layout(cr, layout)
- def main():
- args = get_parser().parse_args()
- app = App(args)
- app.run()
- if __name__ == "__main__":
- main()
|