screenkey.py (screenkey-1.4) | : | screenkey.py (screenkey-1.5) | ||
---|---|---|---|---|
skipping to change at line 20 | skipping to change at line 20 | |||
import json | import json | |||
import os | import os | |||
import subprocess | import subprocess | |||
import numbers | import numbers | |||
from tempfile import NamedTemporaryFile | from tempfile import NamedTemporaryFile | |||
import gi | import gi | |||
gi.require_version('Gtk', '3.0') | gi.require_version('Gtk', '3.0') | |||
gi.require_version('Pango', '1.0') | gi.require_version('Pango', '1.0') | |||
from gi.repository import GLib | from gi.repository import GLib, Gtk, Gdk, GdkPixbuf, Pango, GObject | |||
GLib.threads_init() | ||||
from gi.repository import Gtk, Gdk, GdkPixbuf, Pango, GObject | ||||
import cairo | import cairo | |||
# Gtk shortcuts | # Gtk shortcuts | |||
START = Gtk.Align.START | START = Gtk.Align.START | |||
CENTER = Gtk.Align.CENTER | CENTER = Gtk.Align.CENTER | |||
END = Gtk.Align.END | END = Gtk.Align.END | |||
FILL = Gtk.Align.FILL | FILL = Gtk.Align.FILL | |||
TOP = Gtk.PositionType.TOP | TOP = Gtk.PositionType.TOP | |||
BOTTOM = Gtk.PositionType.BOTTOM | BOTTOM = Gtk.PositionType.BOTTOM | |||
RIGHT = Gtk.PositionType.RIGHT | RIGHT = Gtk.PositionType.RIGHT | |||
skipping to change at line 87 | skipping to change at line 84 | |||
try: | try: | |||
gi.require_version(module, version) | gi.require_version(module, version) | |||
return True | return True | |||
except ValueError: | except ValueError: | |||
return False | return False | |||
class Screenkey(Gtk.Window): | class Screenkey(Gtk.Window): | |||
STATE_FILE = os.path.join(GLib.get_user_config_dir(), 'screenkey.json') | STATE_FILE = os.path.join(GLib.get_user_config_dir(), 'screenkey.json') | |||
def __init__(self, logger, options, show_settings=False): | def __init__(self, logger, options, show_settings=False): | |||
Gtk.Window.__init__(self, Gtk.WindowType.POPUP) | ||||
self.logger = logger | self.logger = logger | |||
self.logger.debug("{} {}".format(APP_NAME, VERSION)) | self.logger.debug("{} {}".format(APP_NAME, VERSION)) | |||
self.exit_status = None | self.exit_status = None | |||
self.timer_hide = None | self.timer_hide = None | |||
self.timer_min = None | self.timer_min = None | |||
defaults = Options({'no_systray': False, | defaults = Options({'no_systray': False, | |||
'timeout': 2.5, | 'timeout': 2.5, | |||
'recent_thr': 0.1, | 'recent_thr': 0.1, | |||
'compr_cnt': 3, | 'compr_cnt': 3, | |||
'ignore': [], | 'ignore': [], | |||
'position': 'bottom', | 'position': 'bottom', | |||
'persist': False, | 'persist': False, | |||
'window': False, | ||||
'font_desc': 'Sans Bold', | 'font_desc': 'Sans Bold', | |||
'font_size': 'medium', | 'font_size': 'medium', | |||
'font_color': 'white', | 'font_color': 'white', | |||
'bg_color': 'black', | 'bg_color': 'black', | |||
'opacity': 0.8, | 'opacity': 0.8, | |||
'key_mode': 'composed', | 'key_mode': 'composed', | |||
'bak_mode': 'baked', | 'bak_mode': 'baked', | |||
'mods_mode': 'normal', | 'mods_mode': 'normal', | |||
'mods_only': False, | 'mods_only': False, | |||
'multiline': False, | 'multiline': False, | |||
skipping to change at line 134 | skipping to change at line 130 | |||
# copy missing defaults | # copy missing defaults | |||
for k, v in defaults.items(): | for k, v in defaults.items(): | |||
if k not in self.options: | if k not in self.options: | |||
self.options[k] = v | self.options[k] = v | |||
if options is not None: | if options is not None: | |||
# override with values from constructor | # override with values from constructor | |||
for k, v in options.items(): | for k, v in options.items(): | |||
if v is not None: | if v is not None: | |||
self.options[k] = v | self.options[k] = v | |||
if not self.options.window: | ||||
Gtk.Window.__init__(self, Gtk.WindowType.POPUP) | ||||
else: | ||||
self.options.persist = True | ||||
Gtk.Window.__init__(self, Gtk.WindowType.TOPLEVEL) | ||||
self.set_keep_above(True) | self.set_keep_above(True) | |||
self.set_accept_focus(False) | self.set_accept_focus(False) | |||
self.set_focus_on_map(False) | self.set_focus_on_map(False) | |||
self.set_app_paintable(True) | self.set_app_paintable(True) | |||
self.button_pixbufs = [] | self.button_pixbufs = [] | |||
self.button_states = [None] * 11 | self.button_states = [None] * 11 | |||
self.img = Gtk.Image() | self.img = Gtk.Image() | |||
self.update_image_tag = None | self.update_image_tag = None | |||
skipping to change at line 163 | skipping to change at line 165 | |||
self.font = Pango.FontDescription(self.options.font_desc) | self.font = Pango.FontDescription(self.options.font_desc) | |||
self.update_colors() | self.update_colors() | |||
self.update_mouse_enabled() | self.update_mouse_enabled() | |||
self.set_size_request(0, 0) | self.set_size_request(0, 0) | |||
self.set_gravity(Gdk.Gravity.CENTER) | self.set_gravity(Gdk.Gravity.CENTER) | |||
self.connect("configure-event", self.on_configure) | self.connect("configure-event", self.on_configure) | |||
self.connect("draw", self.on_draw) | self.connect("draw", self.on_draw) | |||
scr = self.get_screen() | scr = self.get_screen() | |||
scr.connect("size-changed", self.on_size_changed) | scr.connect("size-changed", self.on_screen_size_changed) | |||
scr.connect("monitors-changed", self.on_monitors_changed) | scr.connect("monitors-changed", self.on_monitors_changed) | |||
self.set_active_monitor(self.options.screen) | self.set_active_monitor(self.options.screen) | |||
visual = scr.get_rgba_visual() | visual = scr.get_rgba_visual() | |||
if visual is not None: | if visual is not None: | |||
self.set_visual(visual) | self.set_visual(visual) | |||
self.box.pack_start(self.img, expand=False, fill=True, padding=0) | self.box.pack_start(self.img, expand=False, fill=True, padding=0) | |||
self.box.pack_end(self.label, expand=True, fill=True, padding=0) | self.box.pack_end(self.label, expand=True, fill=True, padding=0) | |||
skipping to change at line 221 | skipping to change at line 223 | |||
# compatibility with previous versions (0.5) | # compatibility with previous versions (0.5) | |||
if options and options.key_mode == 'normal': | if options and options.key_mode == 'normal': | |||
options.key_mode = 'composed' | options.key_mode = 'composed' | |||
return options | return options | |||
def store_state(self, options): | def store_state(self, options): | |||
"""Store options""" | """Store options""" | |||
try: | try: | |||
with open(self.STATE_FILE, 'w') as f: | with open(self.STATE_FILE, 'w') as f: | |||
json.dump(options._store, f) | json.dump(options._store, f, indent=4) | |||
self.logger.debug("Options saved.") | self.logger.debug("Options saved.") | |||
except OSError: | except OSError: | |||
self.logger.debug("Cannot open %s." % self.STATE_FILE) | self.logger.debug("Cannot open %s." % self.STATE_FILE) | |||
def set_active_monitor(self, monitor): | def set_active_monitor(self, monitor): | |||
scr = self.get_screen() | scr = self.get_screen() | |||
if monitor >= scr.get_n_monitors(): | if monitor >= scr.get_n_monitors(): | |||
self.monitor = 0 | self.monitor = 0 | |||
else: | else: | |||
self.monitor = monitor | self.monitor = monitor | |||
self.update_geometry() | self.update_geometry() | |||
def on_monitors_changed(self, *_): | def on_monitors_changed(self, monitor): | |||
self.set_active_monitor(self.monitor) | self.set_active_monitor(self.monitor) | |||
def update_mouse_enabled(self): | def update_mouse_enabled(self): | |||
if self.options.mouse: | if self.options.mouse: | |||
if not self.button_pixbufs: | if not self.button_pixbufs: | |||
self.button_pixbufs = load_button_pixbufs( | self.button_pixbufs = load_button_pixbufs( | |||
Gdk.color_parse(self.options.font_color) | Gdk.color_parse(self.options.font_color) | |||
) | ) | |||
self.img.show() | self.img.show() | |||
self.update_image_tag = GLib.idle_add(self.update_image) | self.update_image_tag = GLib.idle_add(self.update_image) | |||
skipping to change at line 322 | skipping to change at line 324 | |||
def on_draw(self, widget, cr): | def on_draw(self, widget, cr): | |||
cr.set_source_rgba(self.bg_color.red_float, | cr.set_source_rgba(self.bg_color.red_float, | |||
self.bg_color.green_float, | self.bg_color.green_float, | |||
self.bg_color.blue_float, | self.bg_color.blue_float, | |||
self.options.opacity) | self.options.opacity) | |||
cr.set_operator(cairo.OPERATOR_SOURCE) | cr.set_operator(cairo.OPERATOR_SOURCE) | |||
cr.paint() | cr.paint() | |||
cr.set_operator(cairo.OPERATOR_OVER) | cr.set_operator(cairo.OPERATOR_OVER) | |||
return False | return False | |||
def on_configure(self, *_): | def on_configure(self, event, data): | |||
# set event mask for click-through | # set event mask for click-through | |||
self.input_shape_combine_region(cairo.Region(cairo.RectangleInt(0, 0, 0, 0))) | self.input_shape_combine_region(cairo.Region(cairo.RectangleInt(0, 0, 0, 0))) | |||
def on_size_changed(self): | def on_screen_size_changed(self, screen): | |||
self.width, self.height = self.get_size() | self.width, self.height = self.get_size() | |||
self.update_font() | self.update_font() | |||
self.update_image() | self.update_image() | |||
def update_geometry(self, configure=False): | def update_geometry(self, configure=False): | |||
geometry = self.get_screen().get_monitor_geometry(self.monitor) | geometry = self.get_screen().get_monitor_geometry(self.monitor) | |||
if self.options.geometry is not None: | if self.options.geometry is not None: | |||
# NOTE: this assume a single global scaling factor for all | # NOTE: this assume a single global scaling factor for all | |||
# monitors which seems to be true for GTK3: | # monitors which seems to be true for GTK3: | |||
skipping to change at line 411 | skipping to change at line 413 | |||
msg.run() | msg.run() | |||
msg.destroy() | msg.destroy() | |||
self.quit(exit_status=os.EX_SOFTWARE) | self.quit(exit_status=os.EX_SOFTWARE) | |||
def timed_show(self): | def timed_show(self): | |||
if not self.get_property('visible'): | if not self.get_property('visible'): | |||
self.show() | self.show() | |||
if self.timer_hide is not None: | if self.timer_hide is not None: | |||
GObject.source_remove(self.timer_hide) | GObject.source_remove(self.timer_hide) | |||
self.timer_hide = None | self.timer_hide = None | |||
if self.options.timeout > 0 and not any(b and b.pressed for b in self.bu | if self.options.timeout > 0: | |||
tton_states): | # hide automatically if mouse mode is disabled. keep the | |||
self.timer_hide = GObject.timeout_add(self.options.timeout * 1000, s | # window around otherwise as long as any of the visible keys | |||
elf.on_timeout_main) | # (mouse or modifiers) is still held | |||
if not self.options.mouse or \ | ||||
not any(b and b.pressed for b in self.button_states): | ||||
self.timer_hide = GObject.timeout_add(self.options.timeout * 100 | ||||
0, self.on_timeout_main) | ||||
def on_label_change(self, markup, synthetic): | def on_label_change(self, markup, synthetic): | |||
if markup is None: | if markup is None: | |||
self.on_labelmngr_error() | self.on_labelmngr_error() | |||
return | return | |||
_, attr, text, _ = Pango.parse_markup(markup, -1, '\0') | _, attr, text, _ = Pango.parse_markup(markup, -1, '\0') | |||
self.label.set_text(text) | self.label.set_text(text) | |||
self.label.set_attributes(attr) | self.label.set_attributes(attr) | |||
self.update_font() | self.update_font() | |||
skipping to change at line 988 | skipping to change at line 995 | |||
) | ) | |||
about.set_website(APP_URL) | about.set_website(APP_URL) | |||
about.set_icon_name('preferences-desktop-keyboard-shortcuts') | about.set_icon_name('preferences-desktop-keyboard-shortcuts') | |||
about.set_logo_icon_name('preferences-desktop-keyboard-shortcuts') | about.set_logo_icon_name('preferences-desktop-keyboard-shortcuts') | |||
about.connect("response", lambda *_: about.hide_on_delete()) | about.connect("response", lambda *_: about.hide_on_delete()) | |||
about.connect("delete-event", lambda *_: about.hide_on_delete()) | about.connect("delete-event", lambda *_: about.hide_on_delete()) | |||
def on_about_dialog(self, widget, data=None): | def on_about_dialog(self, widget, data=None): | |||
self.about.show() | self.about.show() | |||
def start_lockscreen_detection(self): | ||||
from re import match | ||||
from threading import Thread | ||||
from dbus import SessionBus | ||||
from dbus.mainloop.glib import DBusGMainLoop | ||||
def filter_bus_message(bus, message): | ||||
message_member = message.get_member() | ||||
if not self.enabled or message_member != "ActiveChanged": | ||||
return | ||||
args_list = message.get_args_list() | ||||
if args_list[0]: | ||||
self.labelmngr.stop() | ||||
self.logger.debug("Lock Screen; Screenkey disabled.") | ||||
else: | ||||
self.restart_labelmanager() | ||||
self.logger.debug("Unlock Screen; Screenkey enabled.") | ||||
def lockscreen_detection_loop(): | ||||
DBusGMainLoop(set_as_default=True) | ||||
session_bus = SessionBus() | ||||
signal_interface = None | ||||
for dbus_string in session_bus.list_names(): | ||||
bus_name = str(dbus_string) | ||||
if match(r"org\.(\w+)\.ScreenSaver", bus_name): | ||||
signal_interface = bus_name | ||||
self.logger.debug(f"DBUS signal interface found: \"{signal_i | ||||
nterface}\" ; password should not show when unlocking the screen.") | ||||
break | ||||
if not signal_interface: | ||||
self.logger.debug("ScreenSaver DBUS signal interface not found; | ||||
beware: password may show when unlocking the screen!") | ||||
del(session_bus) | ||||
DBusGMainLoop(set_as_default=False) | ||||
return | ||||
session_bus.add_match_string(f"type='signal',interface='{signal_inte | ||||
rface}'") | ||||
session_bus.add_message_filter(filter_bus_message) | ||||
mainloop = GLib.MainLoop() | ||||
mainloop.run() | ||||
thread = Thread(target=lockscreen_detection_loop) | ||||
thread.daemon = True | ||||
thread.start() | ||||
def run(self): | def run(self): | |||
self.start_lockscreen_detection() | ||||
Gtk.main() | Gtk.main() | |||
return self.exit_status | return self.exit_status | |||
End of changes. 12 change blocks. | ||||
15 lines changed or deleted | 73 lines changed or added |