autokey-0.90.4/src/lib/qtui/data/api.txt0000664000175000017500000000745111723605036017045 0ustar chrischrisengine.create_abbreviation(folder, description, abbr, contents) Create a text abbreviation engine.create_hotkey(folder, description, modifiers, key, contents) Create a text hotkey engine.create_phrase(folder, description, contents) Create a text phrase engine.get_folder(title) Retrieve a folder by its title engine.get_macro_arguments() Get the arguments supplied to the current script via its macro engine.run_script(description) Run an existing script using its description to look it up engine.set_return_value(val) Store a return value to be used by a phrase macro keyboard.fake_keypress(key, repeat=1) Fake a keypress keyboard.press_key(key) Send a key down event keyboard.release_key(key) Send a key up event keyboard.send_key(key, repeat=1) Send a keyboard event keyboard.send_keys(keyString) Send a sequence of keys via keyboard events keyboard.wait_for_keypress(self, key, modifiers=[], timeOut=10.0) Wait for a keypress or key combination mouse.click_absolute(x, y, button) Send a mouse click relative to the screen (absolute) mouse.click_relative(x, y, button) Send a mouse click relative to the active window mouse.click_relative_self(x, y, button) Send a mouse click relative to the current mouse position mouse.wait_for_click(self, button, timeOut=10.0) Wait for a mouse click clipboard.fill_clipboard(contents) Copy text into the clipboard clipboard.fill_selection(contents) Copy text into the X selection clipboard.get_clipboard() Read text from the clipboard clipboard.get_selection() Read text from the X selection dialog.choose_colour(title="Select Colour") Show a Colour Chooser dialog dialog.choose_directory(title="Select Directory", initialDir="~", rememberAs=None, **kwargs) Show a Directory Chooser dialog dialog.combo_menu(options, title="Choose an option", message="Choose an option", **kwargs) Show a combobox menu dialog.input_dialog(title="Enter a value", message="Enter a value", default="", **kwargs) Show an input dialog dialog.list_menu(options, title="Choose a value", message="Choose a value", default=None, **kwargs) Show a single-selection list menu dialog.list_menu_multi(options, title="Choose one or more values", message="Choose one or more values", defaults=[], **kwargs) Show a multiple-selection list menu dialog.open_file(title="Open File", initialDir="~", fileTypes="*|All Files", rememberAs=None, **kwargs) Show an Open File dialog dialog.password_dialog(title="Enter password", message="Enter password", **kwargs) Show a password input dialog dialog.save_file(title="Save As", initialDir="~", fileTypes="*|All Files", rememberAs=None, **kwargs) Show a Save As dialog store.get_value(key) Get a value store.remove_value(key) Remove a value store.set_value(key, value) Store a value system.create_file(fileName, contents="") Create a file with contents system.exec_command(command, getOutput=True) Execute a shell command window.activate(title, switchDesktop=False, matchClass=False) Activate the specified window, giving it input focus window.close(title, matchClass=False) Close the specified window gracefully window.get_active_class() Get the class of the currently active window window.get_active_geometry() Get the geometry of the currently active window window.get_active_title() Get the visible title of the currently active window window.move_to_desktop(title, deskNum, matchClass=False) Move the specified window to the given desktop window.close(title, xOrigin=-1, yOrigin=-1, width=-1, height=-1, matchClass=False) Resize and/or move the specified window window.set_property(title, action, prop, matchClass=False) Set a property on the given window using the specified action window.switch_desktop(deskNum) Switch to the specified desktop window.wait_for_exist(title, timeOut=5) Wait for window with the given title to be created window.wait_for_focus(title, timeOut=5) Wait for window with the given title to have focus autokey-0.90.4/src/lib/gtkui/data/scriptpage.xml0000664000175000017500000001426111737016061020553 0ustar chrischris True False 5 5 True False 5 (Unsaved) False True True True True False none False False 0 True True in True True 1 True False 0 none True False 5 10 True False Always prompt before executing this script False True True False False 0 True False False 0 Show in notification icon menu False True True False False 0 True False False 1 True False False True 10 2 True False <b>Script Settings</b> True False True 2 autokey-0.90.4/src/lib/gtkui/data/settingswidget.xml0000664000175000017500000002337111736014276021465 0ustar chrischris True False 5 True False 3 4 5 5 True False 0 Abbreviations: GTK_FILL GTK_FILL True False 0 Hotkey: 1 2 GTK_FILL GTK_FILL True False 0 Window Filter: 2 3 GTK_FILL GTK_FILL True False 0 $abbr 1 2 GTK_FILL True False 0 $hotkey 1 2 1 2 GTK_FILL True False 0 $filter 1 2 2 3 GTK_FILL Set False 80 True True True False 2 3 GTK_FILL GTK_FILL gtk-clear False 80 True True True False True 3 4 GTK_FILL GTK_FILL Set False True True True False 2 3 1 2 GTK_FILL GTK_FILL Set False True True True False 2 3 2 3 GTK_FILL GTK_FILL gtk-clear False True True True False True 3 4 1 2 GTK_FILL GTK_FILL gtk-clear False True True True False True 3 4 2 3 GTK_FILL GTK_FILL autokey-0.90.4/src/lib/gtkui/data/folderpage.xml0000664000175000017500000001120711737016061020517 0ustar chrischris True False 0 0 5 5 True False (Unsaved) False True True True True False none False False 0 True False 0 none True False 5 5 10 True False 5 Show in notification icon menu False True True False False True True True 0 True False False True 5 1 True False <b>Folder Settings</b> True True True 1 autokey-0.90.4/src/lib/gtkui/data/mainwindow.xml0000664000175000017500000000732211737016061020566 0ustar chrischris False AutoKey - Configuration 600 400 autokey.svg True False True True 150 True True True in True True True True True False 8 True True False False True True True True end 0 autokey-0.90.4/src/lib/gtkui/data/phrasepage.xml0000664000175000017500000001704111742676351020542 0ustar chrischris True False 5 5 True False 5 (Unsaved) False True True True True False none False False 0 True True in True True 1 True False 0 none True False 5 10 True False Always prompt before pasting this phrase False True True False False 0 True False True 0 Show in notification icon menu False True True False False 0 True False True 1 True False 5 True False Paste using False True 0 False True 2 True False False True 10 3 True False <b>Phrase Settings</b> True False True 2 autokey-0.90.4/src/lib/gtkui/data/settingsdialog.xml0000664000175000017500000011073411744242342021435 0ustar chrischris False 8 AutoKey - Preferences True center-on-parent dialog True False vertical 2 True False end gtk-cancel False True True True False True False False 0 gtk-ok False True True True True True False True False False 1 False True end 0 True True True False 0 0 10 5 5 5 True False 5 True False 0 none True False 12 True False Automatically start AutoKey at login False True True False False True True True 0 Prompt for unsaved changes False True True False False True True True 1 Show a notification icon False True True False False True True True 2 True False True False Notification icon style (requires restart): False True 5 0 True True 3 True False <b>Application</b> True True True 0 True False 0 none True False 12 True False Allow keyboard navigation of popup menu False True True False False True True True 0 Sort menu items with most frequently used first False True True False False True True True 1 True False <b>Popup Menu</b> True True True 1 True False 0 none True False 12 Enable undo by pressing backspace False True True False False True True False <b>Expansions</b> True True True 2 True False General False True False 0 0 10 5 5 5 True False 10 True False 0 none True False 5 5 12 True False 5 True False 0 Hotkey: False True 0 True False 0 $hotkey True True 1 Set False 80 True True True False False True 2 gtk-clear False 80 True True True False True False True 3 True False <b>Toggle monitoring using a hotkey</b> True True True 0 True False 0 none True False 5 5 12 True False 5 True False 0 Hotkey: False True 0 True False 0 $hotkey True True 1 Set False 80 True True True False False True 2 gtk-clear False 80 True True True False True False True 3 True False <b>Show configuration window using a hotkey</b> True True True 1 1 True False Special Hotkeys 1 False True False 10 5 5 5 True False 0 none True False 12 True False True False 0 5 Any Python modules placed in this folder will be available for import by scripts. False True 0 True False select-folder Select A Folder False True 1 True False <b>User Module Folder</b> True 2 True False Script Engine 2 False False True 2 button1 button2 autokey-0.90.4/src/lib/gtkui/data/autokey.svg0000664000175000017500000003221711736013500020065 0ustar chrischris image/svg+xml autokey-0.90.4/src/lib/gtkui/data/hotkeysettings.xml0000664000175000017500000002572611754436675021526 0ustar chrischris False 8 Set Hotkey False True center-on-parent dialog True False vertical 5 True False end gtk-cancel False True True True False True False True 0 gtk-ok False True True True False True False True 1 False True end 0 True False 5 10 True False 5 True Control False True True False False False True 0 Alt False True True False False False True 1 Shift False True True False False False True 2 Super False True True False False False True 3 Hyper False True True False False False True 4 Meta False True True False False False True 5 True True 0 True False True False 0 Key: %s True True 0 Press to Set False True True False False False True 1 True True 1 False False 2 cancelButton okButton autokey-0.90.4/src/lib/gtkui/data/blankpage.xml0000664000175000017500000000105411460665304020335 0ustar chrischris True 0 0 5 5 True autokey-0.90.4/src/lib/gtkui/data/menus.xml0000664000175000017500000000505111736024776017551 0ustar chrischris autokey-0.90.4/src/lib/gtkui/data/renamedialog.xml0000664000175000017500000001555711661306541021053 0ustar chrischris False end 8 False True center-on-parent dialog False True False vertical 2 True False end gtk-cancel True True False False True False False 0 gtk-ok True True True True True False True False False 1 False True end 0 True False True False 10 10 gtk-missing-image False True 0 True False 5 True False 0 Enter a new name: False True 0 100 True True • True False False False True 1 Update name on file system True True False False True True True 2 True True 1 True True 1 button1 button2 autokey-0.90.4/src/lib/gtkui/data/abbrsettings.xml0000664000175000017500000004243411736036663021115 0ustar chrischris False 8 Set Abbreviations True center-on-parent dialog True False vertical True False end gtk-cancel False True True True False True False True 0 gtk-ok False True True True True True False True False True 1 False True end 0 True False 8 True False 5 True True in 180 True True False False True False True True 0 True False 5 True gtk-add False True True True False False True False True 0 gtk-remove False True True True False True False True 1 False True 1 True True 0 True False True False True False Trigger on: False True 5 0 True False True 0 1 True True True 1 False True 0 Remove typed abbreviation False True True False start False True False True 1 Omit trigger character False True True False start False True False True 2 Match phrase case to typed abbreviation False True True False start False True False True 3 Ignore case of typed abbreviation False True True False start False True False True 4 Trigger when typed as part of a word False True True False start False True False True 5 Trigger immediately (don't require a trigger character) False True True False start False True False True 6 False False 1 True True 2 cancelButton okButton All non-word Space and Enter Tab autokey-0.90.4/src/lib/gtkui/data/detectdialog.xml0000664000175000017500000002606211737012705021045 0ustar chrischris False 8 Set Window Filter False True center-on-parent dialog True False vertical 5 True False end gtk-cancel False True True True False True False False 0 gtk-ok False True True True True True False True False False 1 False True end 0 True False 15 True False 0 none True False 10 12 True False vertical 5 True False 0 label False True 0 True False 0 label False True 1 True False <b>Properties of selected window</b> True True True 0 True False 0 none True False 5 12 True False vertical Window class (entire application) False True True False False 0 True True False True 0 Window title False True True False False 0 True True classRadioButton False True 1 True False <b>Property to use</b> True True True 1 True False 2 cancelButton okButton autokey-0.90.4/src/lib/gtkui/data/recorddialog.xml0000664000175000017500000002146511736014276021061 0ustar chrischris 1 20 5 1 10 False 8 Record Script False True center-on-parent dialog True False vertical 2 True False end gtk-cancel False True True True False True False True 0 gtk-ok False True True True True True False True False True 1 False True end 0 True False True False 0 <b>Record a keyboard/mouse script</b> True True True 10 0 Record keyboard events False True True False start False True True True True 1 Record mouse events (experimental) False True True False start False True True True 2 True False 5 True False Start recording after False True 0 True True adjustment1 False False 1 True False seconds False True 2 False True 5 3 False True 2 cancelButton okButton autokey-0.90.4/src/lib/gtkui/data/windowfiltersettings.xml0000664000175000017500000002001211747604440022704 0ustar chrischris False 8 Set Window Filter False True center-on-parent dialog True False vertical 5 True False end gtk-cancel False True True True False True False False 0 gtk-ok False True True True True True False True False False 1 False True end 0 True False True False 0 10 Detect Window Properties False True True True False False False 0 True False 5 True False Regular expression to match: False True 0 True True Regular expression to match the title or class of windows • True 30 True False False True True 1 True True 1 Apply recursively to subfolders and items False True True False False 0 True False True 2 True False 2 cancelButton okButton autokey-0.90.4/src/lib/qtui/folderpage.py0000664000175000017500000000473411724546455017317 0ustar chrischris#!/usr/bin/env python # coding=UTF-8 # # Generated by pykdeuic4 from folderpage.ui on Sun Mar 4 11:39:39 2012 # # WARNING! All changes to this file will be lost. from PyKDE4 import kdecore from PyKDE4 import kdeui from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_FolderPage(object): def setupUi(self, FolderPage): FolderPage.setObjectName(_fromUtf8("FolderPage")) FolderPage.resize(568, 530) self.verticalLayout_2 = QtGui.QVBoxLayout(FolderPage) self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) self.urlLabel = KUrlLabel(FolderPage) self.urlLabel.setAlignment(QtCore.Qt.AlignCenter) self.urlLabel.setObjectName(_fromUtf8("urlLabel")) self.verticalLayout_2.addWidget(self.urlLabel) self.settingsGroupBox = QtGui.QGroupBox(FolderPage) self.settingsGroupBox.setObjectName(_fromUtf8("settingsGroupBox")) self.verticalLayout = QtGui.QVBoxLayout(self.settingsGroupBox) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.showInTrayCheckbox = QtGui.QCheckBox(self.settingsGroupBox) self.showInTrayCheckbox.setObjectName(_fromUtf8("showInTrayCheckbox")) self.verticalLayout.addWidget(self.showInTrayCheckbox) self.kseparator = KSeparator(self.settingsGroupBox) self.kseparator.setObjectName(_fromUtf8("kseparator")) self.verticalLayout.addWidget(self.kseparator) self.settingsWidget = SettingsWidget(self.settingsGroupBox) self.settingsWidget.setObjectName(_fromUtf8("settingsWidget")) self.verticalLayout.addWidget(self.settingsWidget) self.verticalLayout_2.addWidget(self.settingsGroupBox) spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.verticalLayout_2.addItem(spacerItem) self.retranslateUi(FolderPage) QtCore.QMetaObject.connectSlotsByName(FolderPage) def retranslateUi(self, FolderPage): FolderPage.setWindowTitle(kdecore.i18n(_fromUtf8("Form"))) self.urlLabel.setTipText(kdecore.i18n(_fromUtf8("Open the folder in the default file manager"))) self.settingsGroupBox.setTitle(kdecore.i18n(_fromUtf8("Folder Settings"))) self.showInTrayCheckbox.setText(kdecore.i18n(_fromUtf8("Show in notification icon menu"))) from PyKDE4.kdeui import KUrlLabel, KSeparator from configwindow import SettingsWidget autokey-0.90.4/src/lib/qtui/notifier.py0000664000175000017500000001016511744657710017020 0ustar chrischris# -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from PyKDE4.kdeui import KNotification, KSystemTrayIcon, KIcon, KStandardAction, KToggleAction from PyKDE4.kdecore import ki18n, i18n from PyQt4.QtCore import SIGNAL from PyQt4.QtGui import QSystemTrayIcon import popupmenu from autokey.configmanager import * TOOLTIP_RUNNING = ki18n("AutoKey - running") TOOLTIP_PAUSED = ki18n("AutoKey - paused") class Notifier: def __init__(self, app): self.app = app self.configManager = app.configManager self.icon = KSystemTrayIcon(ConfigManager.SETTINGS[NOTIFICATION_ICON]) self.icon.connect(self.icon, SIGNAL("activated(QSystemTrayIcon::ActivationReason)"), self.on_activate) self.build_menu() self.update_tool_tip() if ConfigManager.SETTINGS[SHOW_TRAY_ICON]: self.icon.show() def update_tool_tip(self): if ConfigManager.SETTINGS[SERVICE_RUNNING]: self.icon.setToolTip(TOOLTIP_RUNNING.toString()) self.toggleAction.setChecked(True) else: self.icon.setToolTip(TOOLTIP_PAUSED.toString()) self.toggleAction.setChecked(False) def build_menu(self): if ConfigManager.SETTINGS[SHOW_TRAY_ICON]: # Get phrase folders to add to main menu folders = [] items = [] for folder in self.configManager.allFolders: if folder.showInTrayMenu: folders.append(folder) for item in self.configManager.allItems: if item.showInTrayMenu: items.append(item) # Construct main menu menu = popupmenu.PopupMenu(self.app.service, folders, items, False, "AutoKey") if len(items) > 0: menu.addSeparator() self.toggleAction = KToggleAction(i18n("&Enable Monitoring"), menu) self.toggleAction.connect(self.toggleAction, SIGNAL("triggered()"), self.on_enable_toggled) self.toggleAction.setChecked(self.app.service.is_running()) self.toggleAction.setEnabled(not self.app.serviceDisabled) menu.addAction(self.toggleAction) menu.addAction(KIcon("edit-clear"), i18n("&Hide Icon"), self.on_hide_icon) menu.addAction(KIcon("configure"), i18n("&Show Main Window"), self.on_configure) menu.addAction(KStandardAction.quit(self.on_quit, menu)) self.icon.setContextMenu(menu) def update_visible_status(self): self.icon.setVisible(ConfigManager.SETTINGS[SHOW_TRAY_ICON]) self.build_menu() def hide_icon(self): if ConfigManager.SETTINGS[SHOW_TRAY_ICON]: self.icon.hide() def notify_error(self, message): pass # ---- Signal handlers ---- def on_show_error(self): self.app.exec_in_main(self.app.show_script_error) def on_quit(self): self.app.shutdown() def on_activate(self, reason): if reason == QSystemTrayIcon.ActivationReason(QSystemTrayIcon.Trigger): self.on_configure() def on_configure(self): self.app.show_configure() def on_enable_toggled(self): if self.toggleAction.isChecked(): self.app.unpause_service() else: self.app.pause_service() def on_hide_icon(self): self.icon.hide() ConfigManager.SETTINGS[SHOW_TRAY_ICON] = False autokey-0.90.4/src/lib/qtui/phrasepage.py0000664000175000017500000000732411724546455017324 0ustar chrischris#!/usr/bin/env python # coding=UTF-8 # # Generated by pykdeuic4 from phrasepage.ui on Sun Mar 4 11:39:40 2012 # # WARNING! All changes to this file will be lost. from PyKDE4 import kdecore from PyKDE4 import kdeui from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_PhrasePage(object): def setupUi(self, PhrasePage): PhrasePage.setObjectName(_fromUtf8("PhrasePage")) PhrasePage.resize(540, 421) self.verticalLayout_2 = QtGui.QVBoxLayout(PhrasePage) self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) self.urlLabel = KUrlLabel(PhrasePage) self.urlLabel.setAlignment(QtCore.Qt.AlignCenter) self.urlLabel.setObjectName(_fromUtf8("urlLabel")) self.verticalLayout_2.addWidget(self.urlLabel) self.phraseText = KTextEdit(PhrasePage) self.phraseText.setTabChangesFocus(True) self.phraseText.setLineWrapMode(QtGui.QTextEdit.NoWrap) self.phraseText.setAcceptRichText(False) self.phraseText.setObjectName(_fromUtf8("phraseText")) self.verticalLayout_2.addWidget(self.phraseText) self.settingsGroupBox = QtGui.QGroupBox(PhrasePage) self.settingsGroupBox.setObjectName(_fromUtf8("settingsGroupBox")) self.verticalLayout = QtGui.QVBoxLayout(self.settingsGroupBox) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.promptCheckbox = QtGui.QCheckBox(self.settingsGroupBox) self.promptCheckbox.setObjectName(_fromUtf8("promptCheckbox")) self.verticalLayout.addWidget(self.promptCheckbox) self.showInTrayCheckbox = QtGui.QCheckBox(self.settingsGroupBox) self.showInTrayCheckbox.setObjectName(_fromUtf8("showInTrayCheckbox")) self.verticalLayout.addWidget(self.showInTrayCheckbox) self.horizontalLayout_2 = QtGui.QHBoxLayout() self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2")) self.label = QtGui.QLabel(self.settingsGroupBox) self.label.setObjectName(_fromUtf8("label")) self.horizontalLayout_2.addWidget(self.label) self.sendModeCombo = QtGui.QComboBox(self.settingsGroupBox) self.sendModeCombo.setObjectName(_fromUtf8("sendModeCombo")) self.horizontalLayout_2.addWidget(self.sendModeCombo) spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem) self.verticalLayout.addLayout(self.horizontalLayout_2) self.kseparator = KSeparator(self.settingsGroupBox) self.kseparator.setObjectName(_fromUtf8("kseparator")) self.verticalLayout.addWidget(self.kseparator) self.settingsWidget = SettingsWidget(self.settingsGroupBox) self.settingsWidget.setObjectName(_fromUtf8("settingsWidget")) self.verticalLayout.addWidget(self.settingsWidget) self.verticalLayout_2.addWidget(self.settingsGroupBox) self.retranslateUi(PhrasePage) QtCore.QMetaObject.connectSlotsByName(PhrasePage) def retranslateUi(self, PhrasePage): PhrasePage.setWindowTitle(kdecore.i18n(_fromUtf8("Form"))) self.urlLabel.setTipText(kdecore.i18n(_fromUtf8("Open the phrase in the default text editor"))) self.settingsGroupBox.setTitle(kdecore.i18n(_fromUtf8("Phrase Settings"))) self.promptCheckbox.setText(kdecore.i18n(_fromUtf8("Always prompt before pasting this phrase"))) self.showInTrayCheckbox.setText(kdecore.i18n(_fromUtf8("Show in notification icon menu"))) self.label.setText(kdecore.i18n(_fromUtf8("Paste using"))) from PyKDE4.kdeui import KUrlLabel, KSeparator, KTextEdit from configwindow import SettingsWidget autokey-0.90.4/src/lib/qtui/settingsdialog.py0000664000175000017500000001737011744242342020215 0ustar chrischris# -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys from PyKDE4.kdeui import * from PyKDE4.kio import KFileDialog from PyKDE4.kdecore import i18n, KAutostart from PyQt4.QtGui import * from PyQt4.QtCore import SIGNAL, Qt from autokey.configmanager import * from autokey import iomediator, interface, model from dialogs import GlobalHotkeyDialog import generalsettings, specialhotkeysettings, enginesettings class GeneralSettings(QWidget, generalsettings.Ui_Form): def __init__(self, parent): QWidget.__init__(self, parent) generalsettings.Ui_Form.__init__(self) self.setupUi(self) self.promptToSaveCheckbox.setChecked(ConfigManager.SETTINGS[PROMPT_TO_SAVE]) self.showTrayCheckbox.setChecked(ConfigManager.SETTINGS[SHOW_TRAY_ICON]) #self.allowKbNavCheckbox.setChecked(ConfigManager.SETTINGS[MENU_TAKES_FOCUS]) self.allowKbNavCheckbox.setVisible(False) self.sortByUsageCheckbox.setChecked(ConfigManager.SETTINGS[SORT_BY_USAGE_COUNT]) self.enableUndoCheckbox.setChecked(ConfigManager.SETTINGS[UNDO_USING_BACKSPACE]) def save(self): ConfigManager.SETTINGS[PROMPT_TO_SAVE] = self.promptToSaveCheckbox.isChecked() ConfigManager.SETTINGS[SHOW_TRAY_ICON] = self.showTrayCheckbox.isChecked() #ConfigManager.SETTINGS[MENU_TAKES_FOCUS] = self.allowKbNavCheckbox.isChecked() ConfigManager.SETTINGS[SORT_BY_USAGE_COUNT] = self.sortByUsageCheckbox.isChecked() ConfigManager.SETTINGS[UNDO_USING_BACKSPACE] = self.enableUndoCheckbox.isChecked() class SpecialHotkeySettings(QWidget, specialhotkeysettings.Ui_Form): KEY_MAP = GlobalHotkeyDialog.KEY_MAP REVERSE_KEY_MAP = GlobalHotkeyDialog.REVERSE_KEY_MAP def __init__(self, parent, configManager): QWidget.__init__(self, parent) specialhotkeysettings.Ui_Form.__init__(self) self.setupUi(self) self.configManager = configManager self.showConfigDlg = GlobalHotkeyDialog(parent) self.toggleMonitorDlg = GlobalHotkeyDialog(parent) self.useConfigHotkey = self.__loadHotkey(configManager.configHotkey, self.configKeyLabel, self.showConfigDlg, self.clearConfigButton) self.useServiceHotkey = self.__loadHotkey(configManager.toggleServiceHotkey, self.monitorKeyLabel, self.toggleMonitorDlg, self.clearMonitorButton) def __loadHotkey(self, item, label, dialog, clearButton): dialog.load(item) if item.enabled: key = str(item.hotKey.encode("utf-8")) label.setText(item.get_hotkey_string(key, item.modifiers)) clearButton.setEnabled(True) return True else: label.setText(i18n("(None configured)")) clearButton.setEnabled(False) return False def save(self): configHotkey = self.configManager.configHotkey toggleHotkey = self.configManager.toggleServiceHotkey if configHotkey.enabled: self.configManager.app.hotkey_removed(configHotkey) configHotkey.enabled = self.useConfigHotkey if self.useConfigHotkey: self.showConfigDlg.save(configHotkey) self.configManager.app.hotkey_created(configHotkey) if toggleHotkey.enabled: self.configManager.app.hotkey_removed(toggleHotkey) toggleHotkey.enabled = self.useServiceHotkey if self.useServiceHotkey: self.toggleMonitorDlg.save(toggleHotkey) self.configManager.app.hotkey_created(toggleHotkey) # ---- Signal handlers def on_setConfigButton_pressed(self): self.showConfigDlg.exec_() if self.showConfigDlg.result() == QDialog.Accepted: self.useConfigHotkey = True key = self.showConfigDlg.key modifiers = self.showConfigDlg.build_modifiers() self.configKeyLabel.setText(self.showConfigDlg.targetItem.get_hotkey_string(key, modifiers)) self.clearConfigButton.setEnabled(True) def on_clearConfigButton_pressed(self): self.useConfigHotkey = False self.clearConfigButton.setEnabled(False) self.configKeyLabel.setText(i18n("(None configured)")) self.showConfigDlg.reset() def on_setMonitorButton_pressed(self): self.toggleMonitorDlg.exec_() if self.toggleMonitorDlg.result() == QDialog.Accepted: self.useServiceHotkey = True key = self.toggleMonitorDlg.key modifiers = self.toggleMonitorDlg.build_modifiers() self.monitorKeyLabel.setText(self.toggleMonitorDlg.targetItem.get_hotkey_string(key, modifiers)) self.clearMonitorButton.setEnabled(True) def on_clearMonitorButton_pressed(self): self.useServiceHotkey = False self.clearMonitorButton.setEnabled(False) self.monitorKeyLabel.setText(i18n("(None configured)")) self.toggleMonitorDlg.reset() class EngineSettings(QWidget, enginesettings.Ui_Form): def __init__(self, parent, configManager): QWidget.__init__(self, parent) enginesettings.Ui_Form.__init__(self) self.setupUi(self) self.configManager = configManager if configManager.userCodeDir is not None: self.folderLabel.setText(configManager.userCodeDir) if configManager.userCodeDir in sys.path: sys.path.remove(configManager.userCodeDir) self.path = configManager.userCodeDir def save(self): if self.path is not None: self.configManager.userCodeDir = self.path sys.path.append(self.path) def on_browseButton_pressed(self): path = KFileDialog.getExistingDirectory(self.parentWidget(), i18n("Choose Directory")) if path != '': self.path = path self.folderLabel.setText(self.path) class SettingsDialog(KPageDialog): def __init__(self, parent): KPageDialog.__init__(self, parent) self.app = parent.topLevelWidget().app # Used by GlobalHotkeyDialog self.genPage = self.addPage(GeneralSettings(self), i18n("General")) self.genPage.setIcon(KIcon("preferences-other")) self.hkPage = self.addPage(SpecialHotkeySettings(self, parent.app.configManager), i18n("Special Hotkeys")) self.hkPage.setIcon(KIcon("preferences-desktop-keyboard")) self.ePage = self.addPage(EngineSettings(self, parent.app.configManager), i18n("Script Engine")) self.ePage.setIcon(KIcon("text-x-script")) self.setCaption(i18n("Settings")) def slotButtonClicked(self, button): if button == KDialog.Ok: self.genPage.widget().save() self.hkPage.widget().save() self.ePage.widget().save() self.app.configManager.config_altered(True) self.app.update_notifier_visibility() KDialog.slotButtonClicked(self, button) autokey-0.90.4/src/lib/qtui/dialogs.py0000664000175000017500000005006411754436675016633 0ustar chrischris#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import logging, sys, os, re #from PyKDE4.kdeui import KApplication, KXmlGuiWindow, KStandardAction, KIcon, KTextEdit, KAction, KStandardShortcut from PyKDE4.kdeui import * from PyKDE4.kdecore import i18n from PyQt4.QtGui import * from PyQt4.QtCore import SIGNAL, Qt, QRegExp __all__ = ["validate", "EMPTY_FIELD_REGEX", "AbbrSettingsDialog", "HotkeySettingsDialog", "WindowFilterSettingsDialog", "RecordDialog"] import abbrsettings, hotkeysettings, windowfiltersettings, recorddialog, detectdialog from autokey import model, iomediator WORD_CHAR_OPTIONS = { "All non-word" : model.DEFAULT_WORDCHAR_REGEX, "Space and Enter" : r"[^ \n]", "Tab" : r"[^\t]" } WORD_CHAR_OPTIONS_ORDERED = ["All non-word", "Space and Enter", "Tab"] EMPTY_FIELD_REGEX = re.compile(r"^ *$", re.UNICODE) def validate(expression, message, widget, parent): if not expression: KMessageBox.error(parent, message) if widget is not None: widget.setFocus() return expression class AbbrListItem(QListWidgetItem): def __init__(self, text): QListWidgetItem.__init__(self, text) self.setFlags(self.flags() | Qt.ItemFlags(Qt.ItemIsEditable)) def setData(self, role, value): if value.toString() == "": self.listWidget().itemChanged.emit(self) else: QListWidgetItem.setData(self, role, value) class AbbrSettings(QWidget, abbrsettings.Ui_Form): def __init__(self, parent): QWidget.__init__(self, parent) abbrsettings.Ui_Form.__init__(self) self.setupUi(self) for item in WORD_CHAR_OPTIONS_ORDERED: self.wordCharCombo.addItem(item) self.addButton.setIcon(KIcon("list-add")) self.removeButton.setIcon(KIcon("list-remove")) def on_addButton_pressed(self): item = AbbrListItem("") self.abbrListWidget.addItem(item) self.abbrListWidget.editItem(item) self.removeButton.setEnabled(True) def on_removeButton_pressed(self): item = self.abbrListWidget.takeItem(self.abbrListWidget.currentRow()) if self.abbrListWidget.count() == 0: self.removeButton.setEnabled(False) def on_abbrListWidget_itemChanged(self, item): if EMPTY_FIELD_REGEX.match(item.text()): row = self.abbrListWidget.row(item) self.abbrListWidget.takeItem(row) del item if self.abbrListWidget.count() == 0: self.removeButton.setEnabled(False) def on_abbrListWidget_itemDoubleClicked(self, item): self.abbrListWidget.editItem(item) def on_ignoreCaseCheckbox_stateChanged(self, state): if not self.ignoreCaseCheckbox.isChecked(): self.matchCaseCheckbox.setChecked(False) def on_matchCaseCheckbox_stateChanged(self, state): if self.matchCaseCheckbox.isChecked(): self.ignoreCaseCheckbox.setChecked(True) def on_immediateCheckbox_stateChanged(self, state): if self.immediateCheckbox.isChecked(): self.omitTriggerCheckbox.setChecked(False) self.omitTriggerCheckbox.setEnabled(False) self.wordCharCombo.setEnabled(False) else: self.omitTriggerCheckbox.setEnabled(True) self.wordCharCombo.setEnabled(True) class AbbrSettingsDialog(KDialog): def __init__(self, parent): KDialog.__init__(self, parent) self.widget = AbbrSettings(self) self.setMainWidget(self.widget) self.setButtons(KDialog.ButtonCodes(KDialog.ButtonCode(KDialog.Ok | KDialog.Cancel))) self.setPlainCaption(i18n("Set Abbreviations")) self.setModal(True) #self.connect(self, SIGNAL("okClicked()"), self.on_okClicked) def load(self, item): self.targetItem = item self.widget.abbrListWidget.clear() if model.TriggerMode.ABBREVIATION in item.modes: for abbr in item.abbreviations: self.widget.abbrListWidget.addItem(AbbrListItem(abbr)) self.widget.removeButton.setEnabled(True) self.widget.abbrListWidget.setCurrentRow(0) else: self.widget.removeButton.setEnabled(False) self.widget.removeTypedCheckbox.setChecked(item.backspace) self.__resetWordCharCombo() wordCharRegex = item.get_word_chars() if wordCharRegex in WORD_CHAR_OPTIONS.values(): # Default wordchar regex used for desc, regex in WORD_CHAR_OPTIONS.iteritems(): if item.get_word_chars() == regex: self.widget.wordCharCombo.setCurrentIndex(WORD_CHAR_OPTIONS_ORDERED.index(desc)) break else: # Custom wordchar regex used self.widget.wordCharCombo.addItem(model.extract_wordchars(wordCharRegex)) self.widget.wordCharCombo.setCurrentIndex(len(WORD_CHAR_OPTIONS)) if isinstance(item, model.Folder): self.widget.omitTriggerCheckbox.setVisible(False) else: self.widget.omitTriggerCheckbox.setVisible(True) self.widget.omitTriggerCheckbox.setChecked(item.omitTrigger) if isinstance(item, model.Phrase): self.widget.matchCaseCheckbox.setVisible(True) self.widget.matchCaseCheckbox.setChecked(item.matchCase) else: self.widget.matchCaseCheckbox.setVisible(False) self.widget.ignoreCaseCheckbox.setChecked(item.ignoreCase) self.widget.triggerInsideCheckbox.setChecked(item.triggerInside) self.widget.immediateCheckbox.setChecked(item.immediate) def save(self, item): item.modes.append(model.TriggerMode.ABBREVIATION) item.clear_abbreviations() item.abbreviations = self.get_abbrs() item.backspace = self.widget.removeTypedCheckbox.isChecked() option = unicode(self.widget.wordCharCombo.currentText()) if option in WORD_CHAR_OPTIONS: item.set_word_chars(WORD_CHAR_OPTIONS[option]) else: item.set_word_chars(model.make_wordchar_re(option)) if not isinstance(item, model.Folder): item.omitTrigger = self.widget.omitTriggerCheckbox.isChecked() if isinstance(item, model.Phrase): item.matchCase = self.widget.matchCaseCheckbox.isChecked() item.ignoreCase = self.widget.ignoreCaseCheckbox.isChecked() item.triggerInside = self.widget.triggerInsideCheckbox.isChecked() item.immediate = self.widget.immediateCheckbox.isChecked() def reset(self): self.widget.removeButton.setEnabled(False) self.widget.abbrListWidget.clear() self.__resetWordCharCombo() self.widget.omitTriggerCheckbox.setChecked(False) self.widget.removeTypedCheckbox.setChecked(True) self.widget.matchCaseCheckbox.setChecked(False) self.widget.ignoreCaseCheckbox.setChecked(False) self.widget.triggerInsideCheckbox.setChecked(False) self.widget.immediateCheckbox.setChecked(False) def __resetWordCharCombo(self): self.widget.wordCharCombo.clear() for item in WORD_CHAR_OPTIONS_ORDERED: self.widget.wordCharCombo.addItem(item) self.widget.wordCharCombo.setCurrentIndex(0) def get_abbrs(self): ret = [] for i in range(self.widget.abbrListWidget.count()): text = self.widget.abbrListWidget.item(i).text() ret.append(unicode(text)) return ret def get_abbrs_readable(self): abbrs = self.get_abbrs() if len(abbrs) == 1: return abbrs[0] else: return "[%s]" % ','.join(abbrs) def reset_focus(self): self.widget.addButton.setFocus() def __valid(self): if not validate(len(self.get_abbrs()) > 0, i18n("You must specify at least one abbreviation"), self.widget.addButton, self): return False return True def slotButtonClicked(self, button): if button == KDialog.Ok: if self.__valid(): KDialog.slotButtonClicked(self, button) else: self.load(self.targetItem) KDialog.slotButtonClicked(self, button) class HotkeySettings(QWidget, hotkeysettings.Ui_Form): def __init__(self, parent): QWidget.__init__(self, parent) hotkeysettings.Ui_Form.__init__(self) self.setupUi(self) # ---- Signal handlers def on_setButton_pressed(self): self.setButton.setEnabled(False) self.keyLabel.setText(i18n("Press a key or combination...")) self.grabber = iomediator.KeyGrabber(self.parentWidget()) self.grabber.start() class HotkeySettingsDialog(KDialog): KEY_MAP = { ' ' : "", } REVERSE_KEY_MAP = {} for key, value in KEY_MAP.iteritems(): REVERSE_KEY_MAP[value] = key def __init__(self, parent): KDialog.__init__(self, parent) self.widget = HotkeySettings(self) self.setMainWidget(self.widget) self.setButtons(KDialog.ButtonCodes(KDialog.ButtonCode(KDialog.Ok | KDialog.Cancel))) self.setPlainCaption(i18n("Set Hotkey")) self.setModal(True) self.key = None def load(self, item): self.targetItem = item self.widget.setButton.setEnabled(True) if model.TriggerMode.HOTKEY in item.modes: self.widget.controlButton.setChecked(iomediator.Key.CONTROL in item.modifiers) self.widget.altButton.setChecked(iomediator.Key.ALT in item.modifiers) self.widget.shiftButton.setChecked(iomediator.Key.SHIFT in item.modifiers) self.widget.superButton.setChecked(iomediator.Key.SUPER in item.modifiers) self.widget.hyperButton.setChecked(iomediator.Key.HYPER in item.modifiers) self.widget.metaButton.setChecked(iomediator.Key.META in item.modifiers) key = item.hotKey if key in self.KEY_MAP: keyText = self.KEY_MAP[key] else: keyText = key self._setKeyLabel(keyText) self.key = keyText else: self.reset() def save(self, item): item.modes.append(model.TriggerMode.HOTKEY) # Build modifier list modifiers = self.build_modifiers() keyText = self.key if keyText in self.REVERSE_KEY_MAP: key = self.REVERSE_KEY_MAP[keyText] else: key = keyText assert key != None, "Attempt to set hotkey with no key" item.set_hotkey(modifiers, key) def reset(self): self.widget.controlButton.setChecked(False) self.widget.altButton.setChecked(False) self.widget.shiftButton.setChecked(False) self.widget.superButton.setChecked(False) self.widget.hyperButton.setChecked(False) self.widget.metaButton.setChecked(False) self._setKeyLabel(i18n("(None)")) self.key = None self.widget.setButton.setEnabled(True) def set_key(self, key, modifiers=[]): if self.KEY_MAP.has_key(key): key = self.KEY_MAP[key] self._setKeyLabel(key) self.key = key self.widget.controlButton.setChecked(iomediator.Key.CONTROL in modifiers) self.widget.altButton.setChecked(iomediator.Key.ALT in modifiers) self.widget.shiftButton.setChecked(iomediator.Key.SHIFT in modifiers) self.widget.superButton.setChecked(iomediator.Key.SUPER in modifiers) self.widget.hyperButton.setChecked(iomediator.Key.HYPER in modifiers) self.widget.metaButton.setChecked(iomediator.Key.META in modifiers) self.widget.setButton.setEnabled(True) def cancel_grab(self): self.widget.setButton.setEnabled(True) self._setKeyLabel(self.key) def build_modifiers(self): modifiers = [] if self.widget.controlButton.isChecked(): modifiers.append(iomediator.Key.CONTROL) if self.widget.altButton.isChecked(): modifiers.append(iomediator.Key.ALT) if self.widget.shiftButton.isChecked(): modifiers.append(iomediator.Key.SHIFT) if self.widget.superButton.isChecked(): modifiers.append(iomediator.Key.SUPER) if self.widget.hyperButton.isChecked(): modifiers.append(iomediator.Key.HYPER) if self.widget.metaButton.isChecked(): modifiers.append(iomediator.Key.META) modifiers.sort() return modifiers def slotButtonClicked(self, button): if button == KDialog.Ok: if self.__valid(): KDialog.slotButtonClicked(self, button) else: self.load(self.targetItem) KDialog.slotButtonClicked(self, button) def _setKeyLabel(self, key): self.widget.keyLabel.setText(i18n("Key: ") + key) def __valid(self): if not validate(self.key is not None, i18n("You must specify a key for the hotkey."), None, self): return False return True class GlobalHotkeyDialog(HotkeySettingsDialog): def load(self, item): self.targetItem = item if item.enabled: self.widget.controlButton.setChecked(iomediator.Key.CONTROL in item.modifiers) self.widget.altButton.setChecked(iomediator.Key.ALT in item.modifiers) self.widget.shiftButton.setChecked(iomediator.Key.SHIFT in item.modifiers) self.widget.superButton.setChecked(iomediator.Key.SUPER in item.modifiers) self.widget.hyperButton.setChecked(iomediator.Key.HYPER in item.modifiers) self.widget.metaButton.setChecked(iomediator.Key.META in item.modifiers) key = item.hotKey if key in self.KEY_MAP: keyText = self.KEY_MAP[key] else: keyText = key self._setKeyLabel(keyText) self.key = keyText else: self.reset() def save(self, item): # Build modifier list modifiers = self.build_modifiers() keyText = self.key if keyText in self.REVERSE_KEY_MAP: key = self.REVERSE_KEY_MAP[keyText] else: key = keyText assert key != None, "Attempt to set hotkey with no key" item.set_hotkey(modifiers, key) class WindowFilterSettings(QWidget, windowfiltersettings.Ui_Form): def __init__(self, parent): QWidget.__init__(self, parent) windowfiltersettings.Ui_Form.__init__(self) self.setupUi(self) m = QFontMetrics(QApplication.font()) self.triggerRegexLineEdit.setMinimumWidth(m.width("windowclass.WindowClass")) # ---- Signal handlers def on_detectButton_pressed(self): self.detectButton.setEnabled(False) self.grabber = iomediator.WindowGrabber(self.parentWidget()) self.grabber.start() class WindowFilterSettingsDialog(KDialog): def __init__(self, parent): KDialog.__init__(self, parent) self.widget = WindowFilterSettings(self) self.setMainWidget(self.widget) self.setButtons(KDialog.ButtonCodes(KDialog.ButtonCode(KDialog.Ok | KDialog.Cancel))) self.setPlainCaption(i18n("Set Window Filter")) self.setModal(True) def load(self, item): self.targetItem = item if not isinstance(item, model.Folder): self.widget.recursiveCheckBox.hide() else: self.widget.recursiveCheckBox.show() if not item.has_filter(): self.reset() else: self.widget.triggerRegexLineEdit.setText(item.get_filter_regex()) self.widget.recursiveCheckBox.setChecked(item.isRecursive) def save(self, item): item.set_window_titles(self.get_filter_text()) item.set_filter_recursive(self.get_is_recursive()) def get_is_recursive(self): return self.widget.recursiveCheckBox.isChecked() def reset(self): self.widget.triggerRegexLineEdit.setText("") self.widget.recursiveCheckBox.setChecked(False) def reset_focus(self): self.widget.triggerRegexLineEdit.setFocus() def get_filter_text(self): return unicode(self.widget.triggerRegexLineEdit.text()) def receive_window_info(self, info): self.parentWidget().topLevelWidget().app.exec_in_main(self.__receiveWindowInfo, info) def __receiveWindowInfo(self, info): dlg = DetectDialog(self) dlg.populate(info) dlg.exec_() if dlg.result() == QDialog.Accepted: self.widget.triggerRegexLineEdit.setText(dlg.get_choice()) self.widget.detectButton.setEnabled(True) # --- event handlers --- def slotButtonClicked(self, button): if button == KDialog.Cancel: self.load(self.targetItem) KDialog.slotButtonClicked(self, button) class DetectSettings(QWidget, detectdialog.Ui_Form): def __init__(self, parent): QWidget.__init__(self, parent) detectdialog.Ui_Form.__init__(self) self.setupUi(self) self.kbuttongroup.setSelected(0) class DetectDialog(KDialog): def __init__(self, parent): KDialog.__init__(self, parent) self.widget = DetectSettings(self) self.setMainWidget(self.widget) self.setButtons(KDialog.ButtonCodes(KDialog.ButtonCode(KDialog.Ok | KDialog.Cancel))) self.setPlainCaption(i18n("Window Information")) self.setModal(True) def populate(self, windowInfo): self.widget.titleLabel.setText(i18n("Window title: %1", windowInfo[0])) self.widget.classLabel.setText(i18n("Window class: %1", windowInfo[1])) self.windowInfo = windowInfo def get_choice(self): index = self.widget.kbuttongroup.selected() if index == 0: return self.windowInfo[1] else: return self.windowInfo[0] class RecordSettings(QWidget, recorddialog.Ui_Form): def __init__(self, parent): QWidget.__init__(self, parent) recorddialog.Ui_Form.__init__(self) self.setupUi(self) class RecordDialog(KDialog): def __init__(self, parent, closure): KDialog.__init__(self, parent) self.widget = RecordSettings(self) self.setMainWidget(self.widget) self.setButtons(KDialog.ButtonCodes(KDialog.ButtonCode(KDialog.Ok | KDialog.Cancel))) self.setPlainCaption(i18n("Record Script")) self.setModal(True) self.closure = closure def get_record_keyboard(self): return self.widget.recKeyboardButton.isChecked() def get_record_mouse(self): return self.widget.recMouseButton.isChecked() def get_delay(self): return self.widget.secondsSpinBox.value() def slotButtonClicked(self, button): if button == KDialog.Ok: KDialog.slotButtonClicked(self, button) self.closure(True, self.get_record_keyboard(), self.get_record_mouse(), self.get_delay()) else: self.closure(False, self.get_record_keyboard(), self.get_record_mouse(), self.get_delay()) KDialog.slotButtonClicked(self, button) autokey-0.90.4/src/lib/qtui/hotkeysettings.py0000664000175000017500000001004111754436675020264 0ustar chrischris#!/usr/bin/env python # coding=UTF-8 # # Generated by pykdeuic4 from hotkeysettings.ui on Sun Mar 4 11:39:40 2012 # # WARNING! All changes to this file will be lost. from PyKDE4 import kdecore from PyKDE4 import kdeui from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_Form(object): def setupUi(self, Form): Form.setObjectName(_fromUtf8("Form")) Form.resize(400, 300) self.verticalLayout = QtGui.QVBoxLayout(Form) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.modsLabel = QtGui.QLabel(Form) self.modsLabel.setObjectName(_fromUtf8("modsLabel")) self.verticalLayout.addWidget(self.modsLabel) self.horizontalLayout = QtGui.QHBoxLayout() self.horizontalLayout.setContentsMargins(-1, -1, -1, 10) self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) self.controlButton = KPushButton(Form) self.controlButton.setCheckable(True) self.controlButton.setChecked(False) self.controlButton.setObjectName(_fromUtf8("controlButton")) self.horizontalLayout.addWidget(self.controlButton) self.altButton = KPushButton(Form) self.altButton.setCheckable(True) self.altButton.setChecked(False) self.altButton.setObjectName(_fromUtf8("altButton")) self.horizontalLayout.addWidget(self.altButton) self.shiftButton = KPushButton(Form) self.shiftButton.setCheckable(True) self.shiftButton.setChecked(False) self.shiftButton.setObjectName(_fromUtf8("shiftButton")) self.horizontalLayout.addWidget(self.shiftButton) self.superButton = KPushButton(Form) self.superButton.setCheckable(True) self.superButton.setChecked(False) self.superButton.setObjectName(_fromUtf8("superButton")) self.horizontalLayout.addWidget(self.superButton) self.hyperButton = KPushButton(Form) self.hyperButton.setCheckable(True) self.hyperButton.setChecked(False) self.hyperButton.setObjectName(_fromUtf8("hyperButton")) self.horizontalLayout.addWidget(self.hyperButton) self.metaButton = KPushButton(Form) self.metaButton.setCheckable(True) self.metaButton.setChecked(False) self.metaButton.setObjectName(_fromUtf8("metaButton")) self.horizontalLayout.addWidget(self.metaButton) self.verticalLayout.addLayout(self.horizontalLayout) self.horizontalLayout_2 = QtGui.QHBoxLayout() self.horizontalLayout_2.setContentsMargins(-1, -1, -1, 5) self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2")) self.keyLabel = QtGui.QLabel(Form) self.keyLabel.setObjectName(_fromUtf8("keyLabel")) self.horizontalLayout_2.addWidget(self.keyLabel) self.setButton = QtGui.QPushButton(Form) self.setButton.setObjectName(_fromUtf8("setButton")) self.horizontalLayout_2.addWidget(self.setButton) self.verticalLayout.addLayout(self.horizontalLayout_2) self.kseparator = KSeparator(Form) self.kseparator.setObjectName(_fromUtf8("kseparator")) self.verticalLayout.addWidget(self.kseparator) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): Form.setWindowTitle(kdecore.i18n(_fromUtf8("Form"))) self.modsLabel.setText(kdecore.i18n(_fromUtf8("Modifiers:"))) self.controlButton.setText(kdecore.i18n(_fromUtf8("Control"))) self.altButton.setText(kdecore.i18n(_fromUtf8("Alt"))) self.shiftButton.setText(kdecore.i18n(_fromUtf8("Shift"))) self.superButton.setText(kdecore.i18n(_fromUtf8("Super"))) self.hyperButton.setText(kdecore.i18n(_fromUtf8("Hyper"))) self.metaButton.setText(kdecore.i18n(_fromUtf8("Meta"))) self.keyLabel.setText(kdecore.i18n(_fromUtf8("Key: %s"))) self.setButton.setText(kdecore.i18n(_fromUtf8("Press to set"))) from PyKDE4.kdeui import KSeparator, KPushButton autokey-0.90.4/src/lib/qtui/specialhotkeysettings.py0000664000175000017500000001011711724546455021624 0ustar chrischris#!/usr/bin/env python # coding=UTF-8 # # Generated by pykdeuic4 from specialhotkeysettings.ui on Sun Mar 4 11:39:40 2012 # # WARNING! All changes to this file will be lost. from PyKDE4 import kdecore from PyKDE4 import kdeui from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_Form(object): def setupUi(self, Form): Form.setObjectName(_fromUtf8("Form")) Form.resize(531, 397) self.verticalLayout = QtGui.QVBoxLayout(Form) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.groupBox = QtGui.QGroupBox(Form) self.groupBox.setObjectName(_fromUtf8("groupBox")) self.horizontalLayout_2 = QtGui.QHBoxLayout(self.groupBox) self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2")) self.label = QtGui.QLabel(self.groupBox) self.label.setObjectName(_fromUtf8("label")) self.horizontalLayout_2.addWidget(self.label) self.monitorKeyLabel = QtGui.QLabel(self.groupBox) self.monitorKeyLabel.setObjectName(_fromUtf8("monitorKeyLabel")) self.horizontalLayout_2.addWidget(self.monitorKeyLabel) spacerItem = QtGui.QSpacerItem(269, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem) self.setMonitorButton = KPushButton(self.groupBox) self.setMonitorButton.setObjectName(_fromUtf8("setMonitorButton")) self.horizontalLayout_2.addWidget(self.setMonitorButton) self.clearMonitorButton = KPushButton(self.groupBox) self.clearMonitorButton.setObjectName(_fromUtf8("clearMonitorButton")) self.horizontalLayout_2.addWidget(self.clearMonitorButton) self.verticalLayout.addWidget(self.groupBox) self.groupBox_2 = QtGui.QGroupBox(Form) self.groupBox_2.setObjectName(_fromUtf8("groupBox_2")) self.horizontalLayout = QtGui.QHBoxLayout(self.groupBox_2) self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) self.label_2 = QtGui.QLabel(self.groupBox_2) self.label_2.setObjectName(_fromUtf8("label_2")) self.horizontalLayout.addWidget(self.label_2) self.configKeyLabel = QtGui.QLabel(self.groupBox_2) self.configKeyLabel.setObjectName(_fromUtf8("configKeyLabel")) self.horizontalLayout.addWidget(self.configKeyLabel) spacerItem1 = QtGui.QSpacerItem(269, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem1) self.setConfigButton = KPushButton(self.groupBox_2) self.setConfigButton.setObjectName(_fromUtf8("setConfigButton")) self.horizontalLayout.addWidget(self.setConfigButton) self.clearConfigButton = KPushButton(self.groupBox_2) self.clearConfigButton.setObjectName(_fromUtf8("clearConfigButton")) self.horizontalLayout.addWidget(self.clearConfigButton) self.verticalLayout.addWidget(self.groupBox_2) spacerItem2 = QtGui.QSpacerItem(20, 224, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.verticalLayout.addItem(spacerItem2) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): Form.setWindowTitle(kdecore.i18n(_fromUtf8("Form"))) self.groupBox.setTitle(kdecore.i18n(_fromUtf8("Toggle monitoring using a hotkey"))) self.label.setText(kdecore.i18n(_fromUtf8("Hotkey: "))) self.monitorKeyLabel.setText(kdecore.i18n(_fromUtf8("$hotkey"))) self.setMonitorButton.setText(kdecore.i18n(_fromUtf8("Set"))) self.clearMonitorButton.setText(kdecore.i18n(_fromUtf8("Clear"))) self.groupBox_2.setTitle(kdecore.i18n(_fromUtf8("Show configuration window using a hotkey"))) self.label_2.setText(kdecore.i18n(_fromUtf8("Hotkey: "))) self.configKeyLabel.setText(kdecore.i18n(_fromUtf8("$hotkey"))) self.setConfigButton.setText(kdecore.i18n(_fromUtf8("Set"))) self.clearConfigButton.setText(kdecore.i18n(_fromUtf8("Clear"))) from PyKDE4.kdeui import KPushButton autokey-0.90.4/src/lib/qtui/enginesettings.py0000664000175000017500000000515311724546455020231 0ustar chrischris#!/usr/bin/env python # coding=UTF-8 # # Generated by pykdeuic4 from enginesettings.ui on Sun Mar 4 11:39:39 2012 # # WARNING! All changes to this file will be lost. from PyKDE4 import kdecore from PyKDE4 import kdeui from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_Form(object): def setupUi(self, Form): Form.setObjectName(_fromUtf8("Form")) Form.resize(400, 300) self.verticalLayout_2 = QtGui.QVBoxLayout(Form) self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) self.groupBox = QtGui.QGroupBox(Form) self.groupBox.setObjectName(_fromUtf8("groupBox")) self.verticalLayout = QtGui.QVBoxLayout(self.groupBox) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.label = QtGui.QLabel(self.groupBox) self.label.setWordWrap(True) self.label.setObjectName(_fromUtf8("label")) self.verticalLayout.addWidget(self.label) self.horizontalLayout = QtGui.QHBoxLayout() self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) self.folderLabel = QtGui.QLabel(self.groupBox) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.folderLabel.sizePolicy().hasHeightForWidth()) self.folderLabel.setSizePolicy(sizePolicy) self.folderLabel.setObjectName(_fromUtf8("folderLabel")) self.horizontalLayout.addWidget(self.folderLabel) self.browseButton = QtGui.QPushButton(self.groupBox) self.browseButton.setObjectName(_fromUtf8("browseButton")) self.horizontalLayout.addWidget(self.browseButton) self.verticalLayout.addLayout(self.horizontalLayout) self.verticalLayout_2.addWidget(self.groupBox) spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.verticalLayout_2.addItem(spacerItem) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): Form.setWindowTitle(kdecore.i18n(_fromUtf8("Form"))) self.groupBox.setTitle(kdecore.i18n(_fromUtf8("User Module Folder"))) self.label.setText(kdecore.i18n(_fromUtf8("Any Python modules placed in this folder will be available for import by scripts."))) self.folderLabel.setText(kdecore.i18n(_fromUtf8("None selected"))) self.browseButton.setText(kdecore.i18n(_fromUtf8("Browse"))) autokey-0.90.4/src/lib/qtui/abbrsettings.py0000664000175000017500000001326211724546455017672 0ustar chrischris#!/usr/bin/env python # coding=UTF-8 # # Generated by pykdeuic4 from abbrsettings.ui on Sun Mar 4 11:39:39 2012 # # WARNING! All changes to this file will be lost. from PyKDE4 import kdecore from PyKDE4 import kdeui from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_Form(object): def setupUi(self, Form): Form.setObjectName(_fromUtf8("Form")) Form.resize(571, 350) self.verticalLayout_3 = QtGui.QVBoxLayout(Form) self.verticalLayout_3.setObjectName(_fromUtf8("verticalLayout_3")) self.horizontalLayout_3 = QtGui.QHBoxLayout() self.horizontalLayout_3.setObjectName(_fromUtf8("horizontalLayout_3")) self.verticalLayout_2 = QtGui.QVBoxLayout() self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) self.abbrListWidget = QtGui.QListWidget(Form) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Ignored, QtGui.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.abbrListWidget.sizePolicy().hasHeightForWidth()) self.abbrListWidget.setSizePolicy(sizePolicy) self.abbrListWidget.setAlternatingRowColors(True) self.abbrListWidget.setUniformItemSizes(True) self.abbrListWidget.setObjectName(_fromUtf8("abbrListWidget")) self.verticalLayout_2.addWidget(self.abbrListWidget) self.horizontalLayout_2 = QtGui.QHBoxLayout() self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2")) self.addButton = QtGui.QPushButton(Form) icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap(), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.addButton.setIcon(icon) self.addButton.setFlat(False) self.addButton.setObjectName(_fromUtf8("addButton")) self.horizontalLayout_2.addWidget(self.addButton) self.removeButton = QtGui.QPushButton(Form) self.removeButton.setText(_fromUtf8("")) self.removeButton.setIcon(icon) self.removeButton.setObjectName(_fromUtf8("removeButton")) self.horizontalLayout_2.addWidget(self.removeButton) self.verticalLayout_2.addLayout(self.horizontalLayout_2) self.horizontalLayout_3.addLayout(self.verticalLayout_2) self.verticalLayout = QtGui.QVBoxLayout() self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.horizontalLayout = QtGui.QHBoxLayout() self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) self.triggerOnLabel = QtGui.QLabel(Form) self.triggerOnLabel.setObjectName(_fromUtf8("triggerOnLabel")) self.horizontalLayout.addWidget(self.triggerOnLabel) self.wordCharCombo = KComboBox(Form) self.wordCharCombo.setEditable(True) self.wordCharCombo.setObjectName(_fromUtf8("wordCharCombo")) self.horizontalLayout.addWidget(self.wordCharCombo) spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) self.verticalLayout.addLayout(self.horizontalLayout) self.removeTypedCheckbox = QtGui.QCheckBox(Form) self.removeTypedCheckbox.setObjectName(_fromUtf8("removeTypedCheckbox")) self.verticalLayout.addWidget(self.removeTypedCheckbox) self.omitTriggerCheckbox = QtGui.QCheckBox(Form) self.omitTriggerCheckbox.setObjectName(_fromUtf8("omitTriggerCheckbox")) self.verticalLayout.addWidget(self.omitTriggerCheckbox) self.matchCaseCheckbox = QtGui.QCheckBox(Form) self.matchCaseCheckbox.setObjectName(_fromUtf8("matchCaseCheckbox")) self.verticalLayout.addWidget(self.matchCaseCheckbox) self.ignoreCaseCheckbox = QtGui.QCheckBox(Form) self.ignoreCaseCheckbox.setObjectName(_fromUtf8("ignoreCaseCheckbox")) self.verticalLayout.addWidget(self.ignoreCaseCheckbox) self.triggerInsideCheckbox = QtGui.QCheckBox(Form) self.triggerInsideCheckbox.setObjectName(_fromUtf8("triggerInsideCheckbox")) self.verticalLayout.addWidget(self.triggerInsideCheckbox) self.immediateCheckbox = QtGui.QCheckBox(Form) self.immediateCheckbox.setObjectName(_fromUtf8("immediateCheckbox")) self.verticalLayout.addWidget(self.immediateCheckbox) spacerItem1 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.verticalLayout.addItem(spacerItem1) self.horizontalLayout_3.addLayout(self.verticalLayout) self.verticalLayout_3.addLayout(self.horizontalLayout_3) self.kseparator = KSeparator(Form) self.kseparator.setObjectName(_fromUtf8("kseparator")) self.verticalLayout_3.addWidget(self.kseparator) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): Form.setWindowTitle(kdecore.i18n(_fromUtf8("Form"))) self.triggerOnLabel.setText(kdecore.i18n(_fromUtf8("Trigger on:"))) self.removeTypedCheckbox.setText(kdecore.i18n(_fromUtf8("Remove typed abbreviation"))) self.omitTriggerCheckbox.setText(kdecore.i18n(_fromUtf8("Omit trigger character"))) self.matchCaseCheckbox.setText(kdecore.i18n(_fromUtf8("Match phrase case to typed abbreviation"))) self.ignoreCaseCheckbox.setText(kdecore.i18n(_fromUtf8("Ignore case of typed abbreviation"))) self.triggerInsideCheckbox.setText(kdecore.i18n(_fromUtf8("Trigger when typed as part of a word"))) self.immediateCheckbox.setText(kdecore.i18n(_fromUtf8("Trigger immediately (don\'t require a trigger character)"))) from PyKDE4.kdeui import KSeparator, KComboBox autokey-0.90.4/src/lib/qtui/detectdialog.py0000664000175000017500000000523611726560370017627 0ustar chrischris#!/usr/bin/env python # coding=UTF-8 # # Generated by pykdeuic4 from detectdialog.ui on Sat Mar 10 13:47:59 2012 # # WARNING! All changes to this file will be lost. from PyKDE4 import kdecore from PyKDE4 import kdeui from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_Form(object): def setupUi(self, Form): Form.setObjectName(_fromUtf8("Form")) Form.resize(400, 240) self.verticalLayout = QtGui.QVBoxLayout(Form) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.groupBox = QtGui.QGroupBox(Form) self.groupBox.setLayoutDirection(QtCore.Qt.LeftToRight) self.groupBox.setObjectName(_fromUtf8("groupBox")) self.verticalLayout_2 = QtGui.QVBoxLayout(self.groupBox) self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) self.titleLabel = QtGui.QLabel(self.groupBox) self.titleLabel.setObjectName(_fromUtf8("titleLabel")) self.verticalLayout_2.addWidget(self.titleLabel) self.classLabel = QtGui.QLabel(self.groupBox) self.classLabel.setObjectName(_fromUtf8("classLabel")) self.verticalLayout_2.addWidget(self.classLabel) self.verticalLayout.addWidget(self.groupBox) self.kbuttongroup = KButtonGroup(Form) self.kbuttongroup.setObjectName(_fromUtf8("kbuttongroup")) self.verticalLayout_3 = QtGui.QVBoxLayout(self.kbuttongroup) self.verticalLayout_3.setObjectName(_fromUtf8("verticalLayout_3")) self.classButton = QtGui.QRadioButton(self.kbuttongroup) self.classButton.setObjectName(_fromUtf8("classButton")) self.verticalLayout_3.addWidget(self.classButton) self.titleButton = QtGui.QRadioButton(self.kbuttongroup) self.titleButton.setObjectName(_fromUtf8("titleButton")) self.verticalLayout_3.addWidget(self.titleButton) self.verticalLayout.addWidget(self.kbuttongroup) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): Form.setWindowTitle(kdecore.i18n(_fromUtf8("Form"))) self.groupBox.setTitle(kdecore.i18n(_fromUtf8("Window information of selected window"))) self.titleLabel.setText(kdecore.i18n(_fromUtf8("TextLabel"))) self.classLabel.setText(kdecore.i18n(_fromUtf8("TextLabel"))) self.kbuttongroup.setTitle(kdecore.i18n(_fromUtf8("Window property selection"))) self.classButton.setText(kdecore.i18n(_fromUtf8("Window class (entire application)"))) self.titleButton.setText(kdecore.i18n(_fromUtf8("Window title"))) from PyKDE4.kdeui import KButtonGroup autokey-0.90.4/src/lib/qtui/popupmenu.py0000664000175000017500000001472211723375735017235 0ustar chrischris# -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import logging, sys from PyKDE4.kdeui import KMenu, KAction, KActionMenu, KApplication #from PyKDE4.kdecore import ki18n, KAboutData, KCmdLineArgs from PyQt4.QtGui import QCursor from PyQt4.QtCore import SIGNAL, Qt from autokey.configmanager import * _logger = logging.getLogger("phrase-menu") class MenuBase: def __init__(self, service, folders=[], items=[], onDesktop=True, title=None): self.service = service self.__i = 1 self._onDesktop = onDesktop if title is not None: self.addTitle(title) if ConfigManager.SETTINGS[SORT_BY_USAGE_COUNT]: _logger.debug("Sorting phrase menu by usage count") folders.sort(key=lambda obj: obj.usageCount, reverse=True) items.sort(key=lambda obj: obj.usageCount, reverse=True) else: _logger.debug("Sorting phrase menu by item name/title") folders.sort(key=lambda obj: str(obj)) items.sort(key=lambda obj: str(obj)) if len(folders) == 1 and len(items) == 0 and onDesktop: # Only one folder - create menu with just its folders and items self.addTitle(folders[0].title) for folder in folders[0].folders: subMenuItem = SubMenu(self._getMnemonic(folder.title), self, service, folder.folders, folder.items, False) self.addAction(subMenuItem) if len(folders[0].folders) > 0: self.addSeparator() self._addItemsToSelf(folders[0].items, onDesktop) else: # Create folder section for folder in folders: subMenuItem = SubMenu(self._getMnemonic(folder.title), self, service, folder.folders, folder.items, False) self.addAction(subMenuItem) if len(folders) > 0: self.addSeparator() self._addItemsToSelf(items, onDesktop) def _addItem(self, description, item): action = ItemAction(self, self._getMnemonic(description), item, self.service.item_selected) self.addAction(action) def _addItemsToSelf(self, items, onDesktop): # Create item (script/phrase) section if ConfigManager.SETTINGS[SORT_BY_USAGE_COUNT]: items.sort(key=lambda obj: obj.usageCount, reverse=True) else: items.sort(key=lambda obj: str(obj)) for item in items: if onDesktop: self._addItem(item.get_description(self.service.lastStackState), item) else: self._addItem(item.description, item) def _getMnemonic(self, desc): #if 1 < 10 and '&' not in desc and self._onDesktop: # ret = "&%d - %s" % (self.__i, desc) # self.__i += 1 # return ret #else: # FIXME - menu does not get keyboard focus, so mnemonic is useless return desc class PopupMenu(KMenu, MenuBase): def __init__(self, service, folders=[], items=[], onDesktop=True, title=None): KMenu.__init__(self) MenuBase.__init__(self, service, folders, items, onDesktop, title) #if not ConfigManager.SETTINGS[MENU_TAKES_FOCUS]: self.setFocusPolicy(Qt.StrongFocus) # TODO - this doesn't always work - do something about this class SubMenu(KActionMenu, MenuBase): def __init__(self, title, parent, service, folders=[], items=[], onDesktop=True): KActionMenu.__init__(self, title, parent) MenuBase.__init__(self, service, folders, items, onDesktop) class ItemAction(KAction): def __init__(self, parent, description, item, target): KAction.__init__(self, description, parent) self.item = item self.connect(self, SIGNAL("triggered()"), self.on_triggered) self.connect(self, SIGNAL("actionSig"), target) def on_triggered(self): self.emit(SIGNAL("actionSig"), self.item) # ---- TODO Testing stuff - remove later class MockFolder: def __init__(self, title): self.title = title self.items = [] self.folders = [] def add_item(self, item): self.items.append(item) class MockPhrase: def __init__(self, description): self.description = description def get_description(self, buffer): return self.description class MockExpansionService: lastStackState = "" def __init__(self, app): self.app = app def item_selected(self, item): print item.description self.app.quit() if __name__ == "__main__": myFolder = MockFolder("Some phrases") myFolder.add_item(MockPhrase("phrase 1")) myFolder.add_item(MockPhrase("phrase 2")) myFolder.add_item(MockPhrase("phrase 3")) myPhrases = [] myPhrases.append(MockPhrase("phrase 1")) myPhrases.append(MockPhrase("phrase 2")) myPhrases.append(MockPhrase("phrase 3")) appName = "KApplication" catalog = "" programName = ki18n ("KApplication") version = "1.0" description = ki18n ("KApplication/KMainWindow/KAboutData example") license = KAboutData.License_GPL copyright = ki18n ("(c) 2007 Jim Bublitz") text = ki18n ("none") homePage = "www.riverbankcomputing.com" bugEmail = "jbublitz@nwinternet.com" aboutData = KAboutData (appName, catalog, programName, version, description, license, copyright, text, homePage, bugEmail) KCmdLineArgs.init (sys.argv, aboutData) app = KApplication() menu = PopupMenu(MockExpansionService(app), [myFolder], myPhrases) #menu.show() time.sleep(3) menu.exec_(QCursor.pos()) print "shown" #app.exec_() #print "done" sys.exit() autokey-0.90.4/src/lib/qtui/centralwidget.py0000664000175000017500000001043011725514530020017 0ustar chrischris#!/usr/bin/env python # coding=UTF-8 # # Generated by pykdeuic4 from centralwidget.ui on Wed Mar 7 09:04:11 2012 # # WARNING! All changes to this file will be lost. from PyKDE4 import kdecore from PyKDE4 import kdeui from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_CentralWidget(object): def setupUi(self, CentralWidget): CentralWidget.setObjectName(_fromUtf8("CentralWidget")) CentralWidget.resize(832, 590) self.verticalLayout_2 = QtGui.QVBoxLayout(CentralWidget) self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) self.splitter = QtGui.QSplitter(CentralWidget) self.splitter.setOrientation(QtCore.Qt.Horizontal) self.splitter.setObjectName(_fromUtf8("splitter")) self.treeWidget = AkTreeWidget(self.splitter) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.treeWidget.sizePolicy().hasHeightForWidth()) self.treeWidget.setSizePolicy(sizePolicy) self.treeWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.treeWidget.setDragEnabled(True) self.treeWidget.setDragDropMode(QtGui.QAbstractItemView.InternalMove) self.treeWidget.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) self.treeWidget.setAnimated(True) self.treeWidget.setHeaderHidden(False) self.treeWidget.setColumnCount(3) self.treeWidget.setObjectName(_fromUtf8("treeWidget")) self.treeWidget.header().setVisible(True) self.qwidget_1 = QtGui.QWidget(self.splitter) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(2) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.qwidget_1.sizePolicy().hasHeightForWidth()) self.qwidget_1.setSizePolicy(sizePolicy) self.qwidget_1.setObjectName(_fromUtf8("qwidget_1")) self.verticalLayout = QtGui.QVBoxLayout(self.qwidget_1) self.verticalLayout.setMargin(0) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.stack = QtGui.QStackedWidget(self.qwidget_1) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.stack.sizePolicy().hasHeightForWidth()) self.stack.setSizePolicy(sizePolicy) self.stack.setObjectName(_fromUtf8("stack")) self.folderPage = FolderPage() self.folderPage.setObjectName(_fromUtf8("folderPage")) self.stack.addWidget(self.folderPage) self.phrasePage = PhrasePage() self.phrasePage.setObjectName(_fromUtf8("phrasePage")) self.stack.addWidget(self.phrasePage) self.scriptPage = ScriptPage() self.scriptPage.setObjectName(_fromUtf8("scriptPage")) self.stack.addWidget(self.scriptPage) self.verticalLayout.addWidget(self.stack) self.verticalLayout_2.addWidget(self.splitter) self.listWidget = QtGui.QListWidget(CentralWidget) self.listWidget.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.listWidget.setProperty("showDropIndicator", False) self.listWidget.setAlternatingRowColors(True) self.listWidget.setSelectionMode(QtGui.QAbstractItemView.NoSelection) self.listWidget.setWordWrap(True) self.listWidget.setObjectName(_fromUtf8("listWidget")) self.verticalLayout_2.addWidget(self.listWidget) self.retranslateUi(CentralWidget) self.stack.setCurrentIndex(0) QtCore.QMetaObject.connectSlotsByName(CentralWidget) def retranslateUi(self, CentralWidget): CentralWidget.setWindowTitle(kdecore.i18n(_fromUtf8("Form"))) self.treeWidget.headerItem().setText(0, kdecore.i18n(_fromUtf8("Name"))) self.treeWidget.headerItem().setText(1, kdecore.i18n(_fromUtf8("Abbr."))) self.treeWidget.headerItem().setText(2, kdecore.i18n(_fromUtf8("Hotkey"))) from configwindow import AkTreeWidget, PhrasePage, ScriptPage, FolderPage autokey-0.90.4/src/lib/qtui/configwindow.py0000664000175000017500000015375511726636462017714 0ustar chrischris# -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import logging, sys, os, webbrowser, subprocess, time from PyKDE4.kio import * from PyKDE4.kdeui import * from PyKDE4.kdecore import i18n, ki18n, KUrl from PyQt4.QtGui import * from PyQt4.QtCore import SIGNAL, QVariant, Qt from PyQt4 import Qsci from autokey import common #CONFIG_WINDOW_TITLE = i18n(common.CONFIG_WINDOW_TITLE) ACTION_DESCRIPTION_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data/gui.xml") API_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data/api.txt") from dialogs import * from settingsdialog import SettingsDialog from autokey.configmanager import * from autokey.iomediator import Recorder from autokey import model PROBLEM_MSG_PRIMARY = ki18n("Some problems were found") PROBLEM_MSG_SECONDARY = "%1\n\nYour changes have not been saved." _logger = logging.getLogger("configwindow") def set_url_label(button, path): button.setEnabled(True) if path.startswith(CONFIG_DEFAULT_FOLDER): text = path.replace(CONFIG_DEFAULT_FOLDER, i18n("(Default folder)")) else: text = path.replace(os.path.expanduser("~"), "~") button.setText(text) # TODO elide text? button.setUrl("file://" + path) # ---- Internal widgets import settingswidget class SettingsWidget(QWidget, settingswidget.Ui_SettingsWidget): KEY_MAP = HotkeySettingsDialog.KEY_MAP REVERSE_KEY_MAP = HotkeySettingsDialog.REVERSE_KEY_MAP def __init__(self, parent): QWidget.__init__(self, parent) settingswidget.Ui_SettingsWidget.__init__(self) self.setupUi(self) self.abbrDialog = AbbrSettingsDialog(self) self.hotkeyDialog = HotkeySettingsDialog(self) self.filterDialog = WindowFilterSettingsDialog(self) def load(self, item): self.currentItem = item self.abbrDialog.load(self.currentItem) if model.TriggerMode.ABBREVIATION in item.modes: self.abbrLabel.setText(item.get_abbreviations()) self.clearAbbrButton.setEnabled(True) self.abbrEnabled = True else: self.abbrLabel.setText(i18n("(None configured)")) self.clearAbbrButton.setEnabled(False) self.abbrEnabled = False self.hotkeyDialog.load(self.currentItem) if model.TriggerMode.HOTKEY in item.modes: self.hotkeyLabel.setText(item.get_hotkey_string()) self.clearHotkeyButton.setEnabled(True) self.hotkeyEnabled = True else: self.hotkeyLabel.setText(i18n("(None configured)")) self.clearHotkeyButton.setEnabled(False) self.hotkeyEnabled = False self.filterDialog.load(self.currentItem) self.filterEnabled = False self.clearFilterButton.setEnabled(False) if item.has_filter() or item.inherits_filter(): self.windowFilterLabel.setText(item.get_filter_regex()) if not item.inherits_filter(): self.clearFilterButton.setEnabled(True) self.filterEnabled = True else: self.windowFilterLabel.setText(i18n("(None configured)")) def save(self): # Perform hotkey ungrab if model.TriggerMode.HOTKEY in self.currentItem.modes: self.topLevelWidget().app.hotkey_removed(self.currentItem) self.currentItem.set_modes([]) if self.abbrEnabled: self.abbrDialog.save(self.currentItem) if self.hotkeyEnabled: self.hotkeyDialog.save(self.currentItem) if self.filterEnabled: self.filterDialog.save(self.currentItem) else: self.currentItem.set_window_titles(None) if self.hotkeyEnabled: self.topLevelWidget().app.hotkey_created(self.currentItem) def set_dirty(self): self.topLevelWidget().set_dirty() def validate(self): # Start by getting all applicable information if self.abbrEnabled: abbreviations = self.abbrDialog.get_abbrs() else: abbreviations = [] if self.hotkeyEnabled: modifiers = self.hotkeyDialog.build_modifiers() key = self.hotkeyDialog.key else: modifiers = [] key = None filterExpression = None if self.filterEnabled: filterExpression = self.filterDialog.get_filter_text() elif self.currentItem.parent is not None: r = self.currentItem.parent.get_applicable_regex(True) if r is not None: filterExpression = r.pattern # Validate ret = [] configManager = self.topLevelWidget().app.configManager for abbr in abbreviations: unique, conflicting = configManager.check_abbreviation_unique(abbr, filterExpression, self.currentItem) if not unique: msg = i18n("The abbreviation '%1' is already in use by the %2", abbr, str(conflicting)) f = conflicting.get_applicable_regex() if f is not None: msg += i18n(" for windows matching '%1'.", f.pattern) ret.append(msg) unique, conflicting = configManager.check_hotkey_unique(modifiers, key, filterExpression, self.currentItem) if not unique: msg = i18n("The hotkey '%1' is already in use by the %2", conflicting.get_hotkey_string(), str(conflicting)) f = conflicting.get_applicable_regex() if f is not None: msg += i18n(" for windows matching '%1'.", f.pattern) ret.append(msg) return ret # ---- Signal handlers def on_setAbbrButton_pressed(self): self.abbrDialog.exec_() if self.abbrDialog.result() == QDialog.Accepted: self.set_dirty() self.abbrEnabled = True self.abbrLabel.setText(self.abbrDialog.get_abbrs_readable()) self.clearAbbrButton.setEnabled(True) def on_clearAbbrButton_pressed(self): self.set_dirty() self.abbrEnabled = False self.clearAbbrButton.setEnabled(False) self.abbrLabel.setText(i18n("(None configured)")) self.abbrDialog.reset() def on_setHotkeyButton_pressed(self): self.hotkeyDialog.exec_() if self.hotkeyDialog.result() == QDialog.Accepted: self.set_dirty() self.hotkeyEnabled = True key = self.hotkeyDialog.key modifiers = self.hotkeyDialog.build_modifiers() self.hotkeyLabel.setText(self.currentItem.get_hotkey_string(key, modifiers)) self.clearHotkeyButton.setEnabled(True) def on_clearHotkeyButton_pressed(self): self.set_dirty() self.hotkeyEnabled = False self.clearHotkeyButton.setEnabled(False) self.hotkeyLabel.setText(i18n("(None configured)")) self.hotkeyDialog.reset() def on_setFilterButton_pressed(self): self.filterDialog.exec_() if self.filterDialog.result() == QDialog.Accepted: self.set_dirty() filterText = self.filterDialog.get_filter_text() if filterText != "": self.filterEnabled = True self.clearFilterButton.setEnabled(True) self.windowFilterLabel.setText(filterText) else: self.filterEnabled = False self.clearFilterButton.setEnabled(False) if self.currentItem.inherits_filter(): text = self.currentItem.parent.get_child_filter() else: text = i18n("(None configured)") self.windowFilterLabel.setText(text) def on_clearFilterButton_pressed(self): self.set_dirty() self.filterEnabled = False self.clearFilterButton.setEnabled(False) if self.currentItem.inherits_filter(): text = self.currentItem.parent.get_child_filter() else: text = i18n("(None configured)") self.windowFilterLabel.setText(text) self.filterDialog.reset() import scriptpage class ScriptPage(QWidget, scriptpage.Ui_ScriptPage): def __init__(self): QWidget.__init__(self) scriptpage.Ui_ScriptPage.__init__(self) self.setupUi(self) self.scriptCodeEditor.setUtf8(1) lex = Qsci.QsciLexerPython(self) api = Qsci.QsciAPIs(lex) api.load(API_FILE) api.prepare() self.scriptCodeEditor.setLexer(lex) self.scriptCodeEditor.setBraceMatching(Qsci.QsciScintilla.SloppyBraceMatch) self.scriptCodeEditor.setAutoIndent(True) self.scriptCodeEditor.setBackspaceUnindents(True) self.scriptCodeEditor.setIndentationWidth(4) self.scriptCodeEditor.setIndentationGuides(True) self.scriptCodeEditor.setIndentationsUseTabs(False) self.scriptCodeEditor.setAutoCompletionThreshold(3) self.scriptCodeEditor.setAutoCompletionSource(Qsci.QsciScintilla.AcsAll) self.scriptCodeEditor.setCallTipsStyle(Qsci.QsciScintilla.CallTipsNoContext) lex.setFont(KGlobalSettings.fixedFont()) def load(self, script): self.currentScript = script self.scriptCodeEditor.clear() self.scriptCodeEditor.append(script.code) self.showInTrayCheckbox.setChecked(script.showInTrayMenu) self.promptCheckbox.setChecked(script.prompt) self.settingsWidget.load(script) self.topLevelWidget().set_undo_available(False) self.topLevelWidget().set_redo_available(False) if self.is_new_item(): self.urlLabel.setEnabled(False) self.urlLabel.setText(i18n("(Unsaved)")) else: set_url_label(self.urlLabel, self.currentScript.path) def save(self): self.settingsWidget.save() self.currentScript.code = unicode(self.scriptCodeEditor.text()) self.currentScript.showInTrayMenu = self.showInTrayCheckbox.isChecked() self.currentScript.persist() set_url_label(self.urlLabel, self.currentScript.path) return False def set_item_title(self, title): self.currentScript.description = title def rebuild_item_path(self): self.currentScript.rebuild_path() def is_new_item(self): return self.currentScript.path is None def reset(self): self.load(self.currentScript) self.topLevelWidget().set_undo_available(False) self.topLevelWidget().set_redo_available(False) def set_dirty(self): self.topLevelWidget().set_dirty() def start_record(self): self.scriptCodeEditor.append("\n") def start_key_sequence(self): self.scriptCodeEditor.append("keyboard.send_keys(\"") def end_key_sequence(self): self.scriptCodeEditor.append("\")\n") def append_key(self, key): self.scriptCodeEditor.append(key) def append_hotkey(self, key, modifiers): keyString = self.currentScript.get_hotkey_string(key, modifiers) self.scriptCodeEditor.append(keyString) def append_mouseclick(self, xCoord, yCoord, button, windowTitle): self.scriptCodeEditor.append("mouse.click_relative(%d, %d, %d) # %s\n" % (xCoord, yCoord, int(button), windowTitle)) def undo(self): self.scriptCodeEditor.undo() self.topLevelWidget().set_undo_available(self.scriptCodeEditor.isUndoAvailable()) def redo(self): self.scriptCodeEditor.redo() self.topLevelWidget().set_redo_available(self.scriptCodeEditor.isRedoAvailable()) def validate(self): errors = [] # Check script code code = unicode(self.scriptCodeEditor.text()) if EMPTY_FIELD_REGEX.match(code): errors.append(i18n("The script code can't be empty")) # Check settings errors += self.settingsWidget.validate() if errors: msg = i18n(PROBLEM_MSG_SECONDARY, '\n'.join([str(e) for e in errors])) KMessageBox.detailedError(self.topLevelWidget(), PROBLEM_MSG_PRIMARY.toString(), msg) return len(errors) == 0 # --- Signal handlers def on_scriptCodeEditor_textChanged(self): self.set_dirty() self.topLevelWidget().set_undo_available(self.scriptCodeEditor.isUndoAvailable()) self.topLevelWidget().set_redo_available(self.scriptCodeEditor.isRedoAvailable()) def on_promptCheckbox_stateChanged(self, state): self.set_dirty() def on_showInTrayCheckbox_stateChanged(self, state): self.set_dirty() def on_urlLabel_leftClickedUrl(self, url=None): if url: subprocess.Popen(["/usr/bin/xdg-open", url]) import phrasepage class PhrasePage(QWidget, phrasepage.Ui_PhrasePage): def __init__(self): QWidget.__init__(self) phrasepage.Ui_PhrasePage.__init__(self) self.setupUi(self) self.initialising = True l = model.SEND_MODES.keys() l.sort() for val in l: self.sendModeCombo.addItem(val) self.initialising = False def load(self, phrase): self.currentPhrase = phrase self.phraseText.setPlainText(phrase.phrase) self.showInTrayCheckbox.setChecked(phrase.showInTrayMenu) for k, v in model.SEND_MODES.iteritems(): if v == phrase.sendMode: self.sendModeCombo.setCurrentIndex(self.sendModeCombo.findText(k)) break if self.is_new_item(): self.urlLabel.setEnabled(False) self.urlLabel.setText(i18n("(Unsaved)")) else: set_url_label(self.urlLabel, self.currentPhrase.path) # TODO - re-enable me if restoring predictive functionality #self.predictCheckbox.setChecked(model.TriggerMode.PREDICTIVE in phrase.modes) self.promptCheckbox.setChecked(phrase.prompt) self.settingsWidget.load(phrase) def save(self): self.settingsWidget.save() self.currentPhrase.phrase = unicode(self.phraseText.toPlainText()) self.currentPhrase.showInTrayMenu = self.showInTrayCheckbox.isChecked() self.currentPhrase.sendMode = model.SEND_MODES[str(self.sendModeCombo.currentText())] # TODO - re-enable me if restoring predictive functionality #if self.predictCheckbox.isChecked(): # self.currentPhrase.modes.append(model.TriggerMode.PREDICTIVE) self.currentPhrase.prompt = self.promptCheckbox.isChecked() self.currentPhrase.persist() set_url_label(self.urlLabel, self.currentPhrase.path) return False def set_item_title(self, title): self.currentPhrase.description = title def rebuild_item_path(self): self.currentPhrase.rebuild_path() def is_new_item(self): return self.currentPhrase.path is None def reset(self): self.load(self.currentPhrase) def validate(self): errors = [] # Check phrase content phrase = unicode(self.phraseText.toPlainText()) if EMPTY_FIELD_REGEX.match(phrase): errors.append(i18n("The phrase content can't be empty")) # Check settings errors += self.settingsWidget.validate() if errors: msg = i18n(PROBLEM_MSG_SECONDARY, '\n'.join([str(e) for e in errors])) KMessageBox.detailedError(self.topLevelWidget(), PROBLEM_MSG_PRIMARY.toString(), msg) return len(errors) == 0 def set_dirty(self): self.topLevelWidget().set_dirty() def undo(self): self.phraseText.undo() def redo(self): self.phraseText.redo() def insert_token(self, token): self.phraseText.insertPlainText(token) # --- Signal handlers def on_phraseText_textChanged(self): self.set_dirty() def on_phraseText_undoAvailable(self, state): self.topLevelWidget().set_undo_available(state) def on_phraseText_redoAvailable(self, state): self.topLevelWidget().set_redo_available(state) def on_predictCheckbox_stateChanged(self, state): self.set_dirty() def on_promptCheckbox_stateChanged(self, state): self.set_dirty() def on_showInTrayCheckbox_stateChanged(self, state): self.set_dirty() def on_sendModeCombo_currentIndexChanged(self, index): if not self.initialising: self.set_dirty() def on_urlLabel_leftClickedUrl(self, url=None): if url: subprocess.Popen(["/usr/bin/xdg-open", url]) import folderpage class FolderPage(QWidget, folderpage.Ui_FolderPage): def __init__(self): QWidget.__init__(self) folderpage.Ui_FolderPage.__init__(self) self.setupUi(self) def load(self, folder): self.currentFolder = folder self.showInTrayCheckbox.setChecked(folder.showInTrayMenu) self.settingsWidget.load(folder) if self.is_new_item(): self.urlLabel.setEnabled(False) self.urlLabel.setText(i18n("(Unsaved)")) else: set_url_label(self.urlLabel, self.currentFolder.path) def save(self): self.currentFolder.showInTrayMenu = self.showInTrayCheckbox.isChecked() self.settingsWidget.save() self.currentFolder.persist() set_url_label(self.urlLabel, self.currentFolder.path) return not self.currentFolder.path.startswith(CONFIG_DEFAULT_FOLDER) def set_item_title(self, title): self.currentFolder.title = title def rebuild_item_path(self): self.currentFolder.rebuild_path() def is_new_item(self): return self.currentFolder.path is None def reset(self): self.load(self.currentFolder) def validate(self): # Check settings errors = self.settingsWidget.validate() if errors: msg = i18n(PROBLEM_MSG_SECONDARY, '\n'.join([str(e) for e in errors])) KMessageBox.detailedError(self.topLevelWidget(), PROBLEM_MSG_PRIMARY.toString(), msg) return len(errors) == 0 def set_dirty(self): self.topLevelWidget().set_dirty() # --- Signal handlers def on_showInTrayCheckbox_stateChanged(self, state): self.set_dirty() def on_urlLabel_leftClickedUrl(self, url=None): if url: subprocess.Popen(["/usr/bin/xdg-open", url]) class AkTreeWidget(QTreeWidget): def edit(self, index, trigger, event): if index.column() == 0: return QTreeWidget.edit(self, index, trigger, event) return False def keyPressEvent(self, event): if self.topLevelWidget().is_dirty() and \ (event.matches(QKeySequence.MoveToNextLine) or event.matches(QKeySequence.MoveToPreviousLine)): veto = self.parentWidget().parentWidget().promptToSave() if not veto: QTreeWidget.keyPressEvent(self, event) else: event.ignore() else: QTreeWidget.keyPressEvent(self, event) def mousePressEvent(self, event): if self.topLevelWidget().is_dirty(): veto = self.parentWidget().parentWidget().promptToSave() if not veto: QTreeWidget.mousePressEvent(self, event) QTreeWidget.mouseReleaseEvent(self, event) else: event.ignore() else: QTreeWidget.mousePressEvent(self, event) def dragMoveEvent(self, event): target = self.itemAt(event.pos()) if isinstance(target, FolderWidgetItem): QTreeWidget.dragMoveEvent(self, event) else: event.ignore() def dropEvent(self, event): target = self.itemAt(event.pos()) sources = self.selectedItems() self.parentWidget().parentWidget().move_items(sources, target) import centralwidget class CentralWidget(QWidget, centralwidget.Ui_CentralWidget): def __init__(self, parent, app): QWidget.__init__(self, parent) centralwidget.Ui_CentralWidget.__init__(self) self.setupUi(self) self.set_dirty(False) self.configManager = app.configManager self.recorder = Recorder(self.scriptPage) self.cutCopiedItems = [] [self.treeWidget.setColumnWidth(x, ConfigManager.SETTINGS[COLUMN_WIDTHS][x]) for x in range(3)] hView = self.treeWidget.header() hView.setResizeMode(QHeaderView.ResizeMode(QHeaderView.Interactive|QHeaderView.ResizeToContents)) self.logHandler = ListWidgetHandler(self.listWidget, app) self.listWidget.hide() # Log view context menu def populate_tree(self, config): factory = WidgetItemFactory(config.folders) rootFolders = factory.get_root_folder_list() for item in rootFolders: self.treeWidget.addTopLevelItem(item) self.treeWidget.sortItems(0, Qt.AscendingOrder) self.treeWidget.setCurrentItem(self.treeWidget.topLevelItem(0)) self.on_treeWidget_itemSelectionChanged() def set_splitter(self, winSize): pos = ConfigManager.SETTINGS[HPANE_POSITION] self.splitter.setSizes([pos, winSize.width() - pos]) def set_dirty(self, dirty): self.dirty = dirty def promptToSave(self): if ConfigManager.SETTINGS[PROMPT_TO_SAVE]: result = KMessageBox.questionYesNoCancel(self.topLevelWidget(), i18n("There are unsaved changes. Would you like to save them?")) if result == KMessageBox.Yes: return self.on_save() elif result == KMessageBox.Cancel: return True else: # don't prompt, just save return self.on_save() # ---- Signal handlers def on_treeWidget_customContextMenuRequested(self, position): factory = self.topLevelWidget().guiFactory() menu = factory.container("Context", self.topLevelWidget()) menu.popup(QCursor.pos()) def on_treeWidget_itemChanged(self, item, column): if item is self.treeWidget.selectedItems()[0] and column == 0: newText = unicode(item.text(0)) if validate(not EMPTY_FIELD_REGEX.match(newText), i18n("The name can't be empty."), None, self.topLevelWidget()): self.parentWidget().app.monitor.suspend() self.stack.currentWidget().set_item_title(newText) self.stack.currentWidget().rebuild_item_path() persistGlobal = self.stack.currentWidget().save() self.parentWidget().app.monitor.unsuspend() self.parentWidget().app.config_altered(persistGlobal) self.treeWidget.sortItems(0, Qt.AscendingOrder) else: item.update() def on_treeWidget_itemSelectionChanged(self): modelItems = self.__getSelection() if len(modelItems) == 1: modelItem = modelItems[0] if isinstance(modelItem, model.Folder): self.stack.setCurrentIndex(0) self.folderPage.load(modelItem) elif isinstance(modelItem, model.Phrase): self.stack.setCurrentIndex(1) self.phrasePage.load(modelItem) elif isinstance(modelItem, model.Script): self.stack.setCurrentIndex(2) self.scriptPage.load(modelItem) self.topLevelWidget().update_actions(modelItems, True) self.set_dirty(False) self.parentWidget().cancel_record() else: self.topLevelWidget().update_actions(modelItems, False) def on_new_topfolder(self): result = KMessageBox.questionYesNoCancel(self.topLevelWidget(), i18n("Create folder in the default location?"), "Create Folder", KStandardGuiItem.yes(), KGuiItem(i18n("Create Elsewhere"))) self.topLevelWidget().app.monitor.suspend() if result == KMessageBox.Yes: self.__createFolder(None) elif result != KMessageBox.Cancel: path = KFileDialog.getExistingDirectory(KUrl(), self.topLevelWidget()) if path != "": path = unicode(path) name = os.path.basename(path) folder = model.Folder(name, path=path) newItem = FolderWidgetItem(None, folder) self.treeWidget.addTopLevelItem(newItem) self.configManager.folders.append(folder) self.topLevelWidget().app.config_altered(True) self.topLevelWidget().app.monitor.unsuspend() else: self.topLevelWidget().app.monitor.unsuspend() def on_new_folder(self): parentItem = self.treeWidget.selectedItems()[0] self.__createFolder(parentItem) def __createFolder(self, parentItem): folder = model.Folder("New Folder") newItem = FolderWidgetItem(parentItem, folder) self.topLevelWidget().app.monitor.suspend() if parentItem is not None: parentFolder = self.__extractData(parentItem) parentFolder.add_folder(folder) else: self.treeWidget.addTopLevelItem(newItem) self.configManager.folders.append(folder) folder.persist() self.topLevelWidget().app.monitor.unsuspend() self.treeWidget.sortItems(0, Qt.AscendingOrder) self.treeWidget.setCurrentItem(newItem) self.on_treeWidget_itemSelectionChanged() self.on_rename() def on_new_phrase(self): self.topLevelWidget().app.monitor.suspend() parentItem = self.treeWidget.selectedItems()[0] parent = self.__extractData(parentItem) phrase = model.Phrase("New Phrase", "Enter phrase contents") newItem = PhraseWidgetItem(parentItem, phrase) parent.add_item(phrase) phrase.persist() self.topLevelWidget().app.monitor.unsuspend() self.treeWidget.sortItems(0, Qt.AscendingOrder) self.treeWidget.setCurrentItem(newItem) self.treeWidget.setItemSelected(parentItem, False) self.on_treeWidget_itemSelectionChanged() self.on_rename() def on_new_script(self): self.topLevelWidget().app.monitor.suspend() parentItem = self.treeWidget.selectedItems()[0] parent = self.__extractData(parentItem) script = model.Script("New Script", "#Enter script code") newItem = ScriptWidgetItem(parentItem, script) parent.add_item(script) script.persist() self.topLevelWidget().app.monitor.unsuspend() self.treeWidget.sortItems(0, Qt.AscendingOrder) self.treeWidget.setCurrentItem(newItem) self.treeWidget.setItemSelected(parentItem, False) self.on_treeWidget_itemSelectionChanged() self.on_rename() def on_undo(self): self.stack.currentWidget().undo() def on_redo(self): self.stack.currentWidget().redo() def on_copy(self): sourceObjects = self.__getSelection() for source in sourceObjects: if isinstance(source, model.Phrase): newObj = model.Phrase('', '') else: newObj = model.Script('', '') newObj.copy(source) self.cutCopiedItems.append(newObj) def on_clone(self): sourceObject = self.__getSelection()[0] parentItem = self.treeWidget.selectedItems()[0].parent() parent = self.__extractData(parentItem) if isinstance(sourceObject, model.Phrase): newObj = model.Phrase('', '') newObj.copy(sourceObject) newItem = PhraseWidgetItem(parentItem, newObj) else: newObj = model.Script('', '') newObj.copy(sourceObject) newItem = ScriptWidgetItem(parentItem, newObj) parent.add_item(newObj) self.topLevelWidget().app.monitor.suspend() newObj.persist() self.topLevelWidget().app.monitor.unsuspend() self.treeWidget.sortItems(0, Qt.AscendingOrder) self.treeWidget.setCurrentItem(newItem) self.treeWidget.setItemSelected(parentItem, False) self.on_treeWidget_itemSelectionChanged() self.parentWidget().app.config_altered(False) def on_cut(self): self.cutCopiedItems = self.__getSelection() self.topLevelWidget().app.monitor.suspend() sourceItems = self.treeWidget.selectedItems() result = filter(lambda f: f.parent() not in sourceItems, sourceItems) for item in result: self.__removeItem(item) self.topLevelWidget().app.monitor.unsuspend() self.parentWidget().app.config_altered(False) def on_paste(self): parentItem = self.treeWidget.selectedItems()[0] parent = self.__extractData(parentItem) self.topLevelWidget().app.monitor.suspend() newItems = [] for item in self.cutCopiedItems: if isinstance(item, model.Folder): f = WidgetItemFactory(None) newItem = FolderWidgetItem(parentItem, item) f.processFolder(newItem, item) parent.add_folder(item) elif isinstance(item, model.Phrase): newItem = PhraseWidgetItem(parentItem, item) parent.add_item(item) else: newItem = ScriptWidgetItem(parentItem, item) parent.add_item(item) item.persist() newItems.append(newItem) self.treeWidget.sortItems(0, Qt.AscendingOrder) self.treeWidget.setCurrentItem(newItems[-1]) self.on_treeWidget_itemSelectionChanged() self.cutCopiedItems = [] for item in newItems: self.treeWidget.setItemSelected(item, True) self.topLevelWidget().app.monitor.unsuspend() self.parentWidget().app.config_altered(False) def on_delete(self): widgetItems = self.treeWidget.selectedItems() self.topLevelWidget().app.monitor.suspend() if len(widgetItems) == 1: widgetItem = widgetItems[0] data = self.__extractData(widgetItem) if isinstance(data, model.Folder): msg = i18n("Are you sure you want to delete the '%1' folder and all the items in it?", data.title) else: msg = i18n("Are you sure you want to delete '%1'?", data.description) else: msg = i18n("Are you sure you want to delete the %1 selected folders/items?", str(len(widgetItems))) result = KMessageBox.questionYesNo(self.topLevelWidget(), msg) if result == KMessageBox.Yes: for widgetItem in widgetItems: self.__removeItem(widgetItem) self.topLevelWidget().app.monitor.unsuspend() if result == KMessageBox.Yes: self.parentWidget().app.config_altered(False) def on_rename(self): widgetItem = self.treeWidget.selectedItems()[0] self.treeWidget.editItem(widgetItem, 0) def on_save(self): if self.stack.currentWidget().validate(): self.parentWidget().app.monitor.suspend() persistGlobal = self.stack.currentWidget().save() self.topLevelWidget().save_completed(persistGlobal) self.set_dirty(False) item = self.treeWidget.selectedItems()[0] item.update() self.treeWidget.update() self.treeWidget.sortItems(0, Qt.AscendingOrder) self.parentWidget().app.monitor.unsuspend() return False return True def on_reset(self): self.stack.currentWidget().reset() self.set_dirty(False) self.parentWidget().cancel_record() def on_save_log(self): fileName = KFileDialog.getSaveFileName(KUrl(), "", self.parentWidget(), "Save log file") if fileName != "": try: f = open(fileName, 'w') for i in range(self.listWidget.count()): text = self.listWidget.item(i).text() f.write(text) f.write('\n') except: logging.getLogger().exception("Error saving log file") finally: f.close() def on_clear_log(self): self.listWidget.clear() def move_items(self, sourceItems, target): targetModelItem = self.__extractData(target) # Filter out any child objects that belong to a parent already in the list result = filter(lambda f: f.parent() not in sourceItems, sourceItems) self.parentWidget().app.monitor.suspend() for source in result: self.__removeItem(source) sourceModelItem = self.__extractData(source) if isinstance(sourceModelItem, model.Folder): targetModelItem.add_folder(sourceModelItem) self.__moveRecurseUpdate(sourceModelItem) else: targetModelItem.add_item(sourceModelItem) sourceModelItem.path = None sourceModelItem.persist() target.addChild(source) self.parentWidget().app.monitor.unsuspend() self.treeWidget.sortItems(0, Qt.AscendingOrder) self.parentWidget().app.config_altered(True) def __moveRecurseUpdate(self, folder): folder.path = None folder.persist() for subfolder in folder.folders: self.__moveRecurseUpdate(subfolder) for child in folder.items: child.path = None child.persist() # ---- Private methods def get_selected_item(self): return self.__getSelection() def __getSelection(self): items = self.treeWidget.selectedItems() ret = [] for item in items: ret.append(self.__extractData(item)) # Filter out any child objects that belong to a parent already in the list result = filter(lambda f: f.parent not in ret, ret) return result def __extractData(self, item): variant = item.data(3, Qt.UserRole) return variant.toPyObject() def __removeItem(self, widgetItem): parent = widgetItem.parent() item = self.__extractData(widgetItem) self.__deleteHotkeys(item) if parent is None: removedIndex = self.treeWidget.indexOfTopLevelItem(widgetItem) self.treeWidget.takeTopLevelItem(removedIndex) self.configManager.folders.remove(item) else: removedIndex = parent.indexOfChild(widgetItem) parent.removeChild(widgetItem) if isinstance(item, model.Folder): item.parent.remove_folder(item) else: item.parent.remove_item(item) item.remove_data() self.treeWidget.sortItems(0, Qt.AscendingOrder) if parent is not None: if parent.childCount() > 0: newIndex = min([removedIndex, parent.childCount() - 1]) self.treeWidget.setCurrentItem(parent.child(newIndex)) else: self.treeWidget.setCurrentItem(parent) else: newIndex = min([removedIndex, self.treeWidget.topLevelItemCount() - 1]) self.treeWidget.setCurrentItem(self.treeWidget.topLevelItem(newIndex)) def __deleteHotkeys(self, theItem): if model.TriggerMode.HOTKEY in theItem.modes: self.topLevelWidget().app.hotkey_removed(theItem) if isinstance(theItem, model.Folder): for subFolder in theItem.folders: self.__deleteHotkeys(subFolder) for item in theItem.items: if model.TriggerMode.HOTKEY in item.modes: self.topLevelWidget().app.hotkey_removed(item) # ---- Configuration window class ConfigWindow(KXmlGuiWindow): def __init__(self, app): KXmlGuiWindow.__init__(self) self.centralWidget = CentralWidget(self, app) self.setCentralWidget(self.centralWidget) self.app = app # File Menu self.create = self.__createMenuAction("create", i18n("New"), iconName="document-new") self.create.setDelayed(False) self.newTopFolder = self.__createAction("new-top-folder", i18n("Folder"), "folder-new", self.centralWidget.on_new_topfolder) self.newFolder = self.__createAction("new-folder", i18n("Sub-folder"), "folder-new", self.centralWidget.on_new_folder) self.newPhrase = self.__createAction("new-phrase", i18n("Phrase"), "text-x-generic", self.centralWidget.on_new_phrase, KStandardShortcut.New) self.newScript = self.__createAction("new-script", i18n("Script"), "text-x-python", self.centralWidget.on_new_script) self.newScript.setShortcut(QKeySequence("Ctrl+Shift+n")) self.save = self.__createAction("save", i18n("Save"), "document-save", self.centralWidget.on_save, KStandardShortcut.Save) self.create.addAction(self.newTopFolder) self.create.addAction(self.newFolder) self.create.addAction(self.newPhrase) self.create.addAction(self.newScript) self.close = self.__createAction("close-window", i18n("Close Window"), "window-close", self.on_close, KStandardShortcut.Close) KStandardAction.quit(self.on_quit, self.actionCollection()) # Edit Menu self.cut = self.__createAction("cut-item", i18n("Cut Item"), "edit-cut", self.centralWidget.on_cut) self.copy = self.__createAction("copy-item", i18n("Copy Item"), "edit-copy", self.centralWidget.on_copy) self.paste = self.__createAction("paste-item", i18n("Paste Item"), "edit-paste", self.centralWidget.on_paste) self.clone = self.__createAction("clone-item", i18n("Clone Item"), "edit-copy", self.centralWidget.on_clone) #self.cut.setShortcut(QKeySequence("Ctrl+Shift+x")) #self.copy.setShortcut(QKeySequence("Ctrl+Shift+c")) self.clone.setShortcut(QKeySequence("Ctrl+Shift+c")) self.undo = self.__createAction("undo", i18n("Undo"), "edit-undo", self.centralWidget.on_undo, KStandardShortcut.Undo) self.redo = self.__createAction("redo", i18n("Redo"), "edit-redo", self.centralWidget.on_redo, KStandardShortcut.Redo) rename = self.__createAction("rename", i18n("Rename"), None, self.centralWidget.on_rename) rename.setShortcut(QKeySequence("f2")) self.delete = self.__createAction("delete-item", i18n("Delete"), "edit-delete", self.centralWidget.on_delete) self.delete.setShortcut(QKeySequence("Ctrl+d")) self.record = self.__createToggleAction("record", i18n("Record Script"), self.on_record, "media-record") self.run = self.__createAction("run", i18n("Run Script"), "media-playback-start", self.on_run_script) self.run.setShortcut(QKeySequence("f8")) self.insertMacro = self.__createMenuAction("insert-macro", i18n("Insert Macro"), None, None) menu = app.service.phraseRunner.macroManager.get_menu(self.on_insert_macro, self.insertMacro.menu()) # Settings Menu self.enable = self.__createToggleAction("enable-monitoring", i18n("Enable Monitoring"), self.on_enable_toggled) self.advancedSettings = self.__createAction("advanced-settings", i18n("Configure AutoKey"), "configure", self.on_advanced_settings) self.__createAction("script-error", i18n("View script error"), "dialog-error", self.on_show_error) self.showLog = self.__createToggleAction("show-log-view", i18n("Show log view"), self.on_show_log) self.showLog.setShortcut(QKeySequence("f4")) # Help Menu self.__createAction("online-help", i18n("Online Manual"), "help-contents", self.on_show_help) self.__createAction("online-faq", i18n("F.A.Q."), "help-faq", self.on_show_faq) self.__createAction("online-api", i18n("Scripting Help"), None, self.on_show_api) self.__createAction("donate", i18n("Donate"), "face-smile", self.on_donate) self.__createAction("report-bug", i18n("Report a Bug"), "tools-report-bug", self.on_report_bug) self.__createAction("about", i18n("About AutoKey"), "help-about", self.on_about) self.setHelpMenuEnabled(True) # Log view context menu act = self.__createAction("clear-log", i18n("Clear Log"), None, self.centralWidget.on_clear_log) self.centralWidget.listWidget.addAction(act) act = self.__createAction("clear-log", i18n("Save Log As..."), None, self.centralWidget.on_save_log) self.centralWidget.listWidget.addAction(act) #self.createStandardStatusBarAction() # TODO statusbar options = KXmlGuiWindow.Default ^ KXmlGuiWindow.StandardWindowOptions(KXmlGuiWindow.StatusBar) #options = KXmlGuiWindow.Default # TODO statusbar self.setupGUI(options) # Initialise action states self.enable.setChecked(self.app.service.is_running()) self.enable.setEnabled(not self.app.serviceDisabled) self.undo.setEnabled(False) self.redo.setEnabled(False) self.centralWidget.populate_tree(self.app.configManager) self.centralWidget.set_splitter(self.size()) self.setAutoSaveSettings() def set_dirty(self): self.centralWidget.set_dirty(True) self.save.setEnabled(True) def config_modified(self): pass def is_dirty(self): return self.centralWidget.dirty def update_actions(self, items, changed): if len(items) > 0: canCreate = isinstance(items[0], model.Folder) and len(items) == 1 canCopy = True for item in items: if isinstance(item, model.Folder): canCopy = False break self.create.setEnabled(True) self.newTopFolder.setEnabled(True) self.newFolder.setEnabled(canCreate) self.newPhrase.setEnabled(canCreate) self.newScript.setEnabled(canCreate) self.copy.setEnabled(canCopy) self.clone.setEnabled(canCopy) self.paste.setEnabled(canCreate and len(self.centralWidget.cutCopiedItems) > 0) self.record.setEnabled(isinstance(items[0], model.Script) and len(items) == 1) self.run.setEnabled(isinstance(items[0], model.Script) and len(items) == 1) self.insertMacro.setEnabled(isinstance(items[0], model.Phrase) and len(items) == 1) if changed: self.save.setEnabled(False) self.undo.setEnabled(False) self.redo.setEnabled(False) def set_undo_available(self, state): self.undo.setEnabled(state) def set_redo_available(self, state): self.redo.setEnabled(state) def save_completed(self, persistGlobal): self.save.setEnabled(False) self.app.config_altered(persistGlobal) def __createAction(self, actionName, name, iconName=None, target=None, shortcut=None): if iconName is not None: action = KAction(KIcon(iconName), name, self.actionCollection()) else: action = KAction(name, self.actionCollection()) if shortcut is not None: standardShortcut = KStandardShortcut.shortcut(shortcut) action.setShortcut(standardShortcut) if target is not None: self.connect(action, SIGNAL("triggered()"), target) self.actionCollection().addAction(actionName, action) return action def __createToggleAction(self, actionName, name, target, iconName=None): if iconName is not None: action = KToggleAction(KIcon(iconName), name, self.actionCollection()) else: action = KToggleAction(name, self.actionCollection()) if target is not None: self.connect(action, SIGNAL("triggered()"), target) self.actionCollection().addAction(actionName, action) return action def __createMenuAction(self, actionName, name, target=None, iconName=None): if iconName is not None: action = KActionMenu(KIcon(iconName), name, self.actionCollection()) else: action = KActionMenu(name, self.actionCollection()) if target is not None: self.connect(action, SIGNAL("triggered()"), target) self.actionCollection().addAction(actionName, action) return action def cancel_record(self): if self.record.isChecked(): self.record.setChecked(False) self.centralWidget.recorder.stop() # ---- Signal handlers ---- def queryClose(self): ConfigManager.SETTINGS[HPANE_POSITION] = self.centralWidget.splitter.sizes()[0] + 4 l = [] for x in xrange(3): l.append(self.centralWidget.treeWidget.columnWidth(x)) ConfigManager.SETTINGS[COLUMN_WIDTHS] = l if self.is_dirty(): if self.centralWidget.promptToSave(): return False self.hide() logging.getLogger().removeHandler(self.centralWidget.logHandler) return True # File Menu def on_close(self): self.cancel_record() self.queryClose() def on_quit(self): if self.queryClose(): self.app.shutdown() # Edit Menu def on_insert_macro(self, macro): token = macro.get_token() self.centralWidget.phrasePage.insert_token(token) def on_record(self): if self.record.isChecked(): dlg = RecordDialog(self, self.__doRecord) dlg.show() else: self.centralWidget.recorder.stop() def __doRecord(self, ok, recKb, recMouse, delay): if ok: self.centralWidget.recorder.set_record_keyboard(recKb) self.centralWidget.recorder.set_record_mouse(recMouse) self.centralWidget.recorder.start(delay) else: self.record.setChecked(False) def on_run_script(self): t = threading.Thread(target=self.__runScript) t.start() def __runScript(self): script = self.centralWidget.get_selected_item()[0] time.sleep(2) self.app.service.scriptRunner.execute(script) # Settings Menu def on_enable_toggled(self): if self.enable.isChecked(): self.app.unpause_service() else: self.app.pause_service() def on_advanced_settings(self): s = SettingsDialog(self) s.show() def on_show_log(self): self.centralWidget.listWidget.setVisible(self.showLog.isChecked()) def on_show_error(self): self.app.show_script_error() # Help Menu def on_show_faq(self): webbrowser.open(common.FAQ_URL, False, True) def on_show_help(self): webbrowser.open(common.HELP_URL, False, True) def on_show_api(self): webbrowser.open(common.API_URL, False, True) def on_donate(self): webbrowser.open(common.DONATE_URL, False, True) def on_report_bug(self): webbrowser.open(common.BUG_URL, False, True) def on_about(self): dlg = KAboutApplicationDialog(self.app.aboutData, self) dlg.show() # ---- TreeWidget and helper functions class WidgetItemFactory: def __init__(self, rootFolders): self.folders = rootFolders def get_root_folder_list(self): rootItems = [] for folder in self.folders: item = self.__buildItem(None, folder) rootItems.append(item) self.processFolder(item, folder) return rootItems def processFolder(self, parentItem, parentFolder): for folder in parentFolder.folders: item = self.__buildItem(parentItem, folder) self.processFolder(item, folder) for childModelItem in parentFolder.items: self.__buildItem(parentItem, childModelItem) def __buildItem(self, parent, item): if isinstance(item, model.Folder): return FolderWidgetItem(parent, item) elif isinstance(item, model.Phrase): return PhraseWidgetItem(parent, item) elif isinstance(item, model.Script): return ScriptWidgetItem(parent, item) class FolderWidgetItem(QTreeWidgetItem): def __init__(self, parent, folder): QTreeWidgetItem.__init__(self) self.folder = folder self.setIcon(0, KIcon("folder")) self.setText(0, folder.title) self.setText(1, folder.get_abbreviations()) self.setText(2, folder.get_hotkey_string()) self.setData(3, Qt.UserRole, QVariant(folder)) if parent is not None: parent.addChild(self) self.setFlags(self.flags() | Qt.ItemIsEditable) def update(self): self.setText(0, self.folder.title) self.setText(1, self.folder.get_abbreviations()) self.setText(2, self.folder.get_hotkey_string()) def __ge__(self, other): if isinstance(other, ScriptWidgetItem): return QTreeWidgetItem.__ge__(self, other) else: return False def __lt__(self, other): if isinstance(other, FolderWidgetItem): return QTreeWidgetItem.__lt__(self, other) else: return True class PhraseWidgetItem(QTreeWidgetItem): def __init__(self, parent, phrase): QTreeWidgetItem.__init__(self) self.phrase = phrase self.setIcon(0, KIcon("text-x-generic")) self.setText(0, phrase.description) self.setText(1, phrase.get_abbreviations()) self.setText(2, phrase.get_hotkey_string()) self.setData(3, Qt.UserRole, QVariant(phrase)) if parent is not None: parent.addChild(self) self.setFlags(self.flags() | Qt.ItemIsEditable) def update(self): self.setText(0, self.phrase.description) self.setText(1, self.phrase.get_abbreviations()) self.setText(2, self.phrase.get_hotkey_string()) def __ge__(self, other): if isinstance(other, ScriptWidgetItem): return QTreeWidgetItem.__ge__(self, other) else: return True def __lt__(self, other): if isinstance(other, PhraseWidgetItem): return QTreeWidgetItem.__lt__(self, other) else: return False class ScriptWidgetItem(QTreeWidgetItem): def __init__(self, parent, script): QTreeWidgetItem.__init__(self) self.script = script self.setIcon(0, KIcon("text-x-python")) self.setText(0, script.description) self.setText(1, script.get_abbreviations()) self.setText(2, script.get_hotkey_string()) self.setData(3, Qt.UserRole, QVariant(script)) if parent is not None: parent.addChild(self) self.setFlags(self.flags() | Qt.ItemIsEditable) def update(self): self.setText(0, self.script.description) self.setText(1, self.script.get_abbreviations()) self.setText(2, self.script.get_hotkey_string()) def __ge__(self, other): if isinstance(other, ScriptWidgetItem): return QTreeWidgetItem.__ge__(self, other) else: return True def __lt__(self, other): if isinstance(other, ScriptWidgetItem): return QTreeWidgetItem.__lt__(self, other) else: return False class ListWidgetHandler(logging.Handler): def __init__(self, listWidget, app): logging.Handler.__init__(self) self.widget = listWidget self.app = app self.level = logging.DEBUG rootLogger = logging.getLogger() logFormat = "%(message)s" rootLogger.addHandler(self) self.setFormatter(logging.Formatter(logFormat)) def flush(self): pass def emit(self, record): try: item = QListWidgetItem(self.format(record)) if record.levelno > logging.INFO: item.setIcon(KIcon("dialog-warning")) item.setForeground(QBrush(Qt.red)) else: item.setIcon(KIcon("dialog-information")) self.app.exec_in_main(self.__addItem, item) except (KeyboardInterrupt, SystemExit): raise except: self.handleError(record) def __addItem(self, item): self.widget.addItem(item) if self.widget.count() > 50: delItem = self.widget.takeItem(0) del delItem self.widget.scrollToBottom()autokey-0.90.4/src/lib/qtui/recorddialog.py0000664000175000017500000000466411724546455017647 0ustar chrischris#!/usr/bin/env python # coding=UTF-8 # # Generated by pykdeuic4 from recorddialog.ui on Sun Mar 4 11:39:40 2012 # # WARNING! All changes to this file will be lost. from PyKDE4 import kdecore from PyKDE4 import kdeui from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_Form(object): def setupUi(self, Form): Form.setObjectName(_fromUtf8("Form")) Form.resize(400, 300) self.verticalLayout = QtGui.QVBoxLayout(Form) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.label = QtGui.QLabel(Form) self.label.setObjectName(_fromUtf8("label")) self.verticalLayout.addWidget(self.label) self.recKeyboardButton = QtGui.QCheckBox(Form) self.recKeyboardButton.setObjectName(_fromUtf8("recKeyboardButton")) self.verticalLayout.addWidget(self.recKeyboardButton) self.recMouseButton = QtGui.QCheckBox(Form) self.recMouseButton.setObjectName(_fromUtf8("recMouseButton")) self.verticalLayout.addWidget(self.recMouseButton) self.horizontalLayout = QtGui.QHBoxLayout() self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) self.label_2 = QtGui.QLabel(Form) self.label_2.setObjectName(_fromUtf8("label_2")) self.horizontalLayout.addWidget(self.label_2) self.secondsSpinBox = KIntSpinBox(Form) self.secondsSpinBox.setProperty("value", 5) self.secondsSpinBox.setObjectName(_fromUtf8("secondsSpinBox")) self.horizontalLayout.addWidget(self.secondsSpinBox) self.label_3 = QtGui.QLabel(Form) self.label_3.setObjectName(_fromUtf8("label_3")) self.horizontalLayout.addWidget(self.label_3) self.verticalLayout.addLayout(self.horizontalLayout) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): Form.setWindowTitle(kdecore.i18n(_fromUtf8("Form"))) self.label.setText(kdecore.i18n(_fromUtf8("Record a keyboard/mouse macro"))) self.recKeyboardButton.setText(kdecore.i18n(_fromUtf8("Record keyboard events"))) self.recMouseButton.setText(kdecore.i18n(_fromUtf8("Record mouse events (experimental)"))) self.label_2.setText(kdecore.i18n(_fromUtf8("Start recording after"))) self.label_3.setText(kdecore.i18n(_fromUtf8("seconds"))) from PyKDE4.kdeui import KIntSpinBox autokey-0.90.4/src/lib/qtui/windowfiltersettings.py0000664000175000017500000000557711726560370021505 0ustar chrischris#!/usr/bin/env python # coding=UTF-8 # # Generated by pykdeuic4 from windowfiltersettings.ui on Sat Mar 10 15:51:01 2012 # # WARNING! All changes to this file will be lost. from PyKDE4 import kdecore from PyKDE4 import kdeui from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_Form(object): def setupUi(self, Form): Form.setObjectName(_fromUtf8("Form")) Form.resize(425, 120) self.verticalLayout = QtGui.QVBoxLayout(Form) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.horizontalLayout = QtGui.QHBoxLayout() self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) self.label_2 = QtGui.QLabel(Form) self.label_2.setObjectName(_fromUtf8("label_2")) self.horizontalLayout.addWidget(self.label_2) self.triggerRegexLineEdit = KLineEdit(Form) self.triggerRegexLineEdit.setUrlDropsEnabled(False) self.triggerRegexLineEdit.setProperty("showClearButton", True) self.triggerRegexLineEdit.setObjectName(_fromUtf8("triggerRegexLineEdit")) self.horizontalLayout.addWidget(self.triggerRegexLineEdit) self.verticalLayout.addLayout(self.horizontalLayout) self.recursiveCheckBox = QtGui.QCheckBox(Form) self.recursiveCheckBox.setObjectName(_fromUtf8("recursiveCheckBox")) self.verticalLayout.addWidget(self.recursiveCheckBox) self.horizontalLayout_2 = QtGui.QHBoxLayout() self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2")) spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem) self.detectButton = QtGui.QPushButton(Form) self.detectButton.setObjectName(_fromUtf8("detectButton")) self.horizontalLayout_2.addWidget(self.detectButton) self.verticalLayout.addLayout(self.horizontalLayout_2) self.kseparator = KSeparator(Form) self.kseparator.setObjectName(_fromUtf8("kseparator")) self.verticalLayout.addWidget(self.kseparator) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): Form.setWindowTitle(kdecore.i18n(_fromUtf8("Form"))) self.label_2.setText(kdecore.i18n(_fromUtf8("Regular expression to match:"))) self.triggerRegexLineEdit.setToolTip(kdecore.i18n(_fromUtf8("Window title"))) self.triggerRegexLineEdit.setWhatsThis(kdecore.i18n(_fromUtf8("Enter a regular expression that matches the title of windows in which you want this item to trigger."))) self.recursiveCheckBox.setText(kdecore.i18n(_fromUtf8("Apply recursively to subfolders and items"))) self.detectButton.setText(kdecore.i18n(_fromUtf8("Detect Window Properties"))) from PyKDE4.kdeui import KSeparator, KLineEdit autokey-0.90.4/src/lib/qtui/generalsettings.py0000664000175000017500000000707111724546455020402 0ustar chrischris#!/usr/bin/env python # coding=UTF-8 # # Generated by pykdeuic4 from generalsettings.ui on Sun Mar 4 11:39:39 2012 # # WARNING! All changes to this file will be lost. from PyKDE4 import kdecore from PyKDE4 import kdeui from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_Form(object): def setupUi(self, Form): Form.setObjectName(_fromUtf8("Form")) Form.resize(491, 444) self.verticalLayout_4 = QtGui.QVBoxLayout(Form) self.verticalLayout_4.setObjectName(_fromUtf8("verticalLayout_4")) self.groupBox = QtGui.QGroupBox(Form) self.groupBox.setObjectName(_fromUtf8("groupBox")) self.verticalLayout = QtGui.QVBoxLayout(self.groupBox) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.promptToSaveCheckbox = QtGui.QCheckBox(self.groupBox) self.promptToSaveCheckbox.setObjectName(_fromUtf8("promptToSaveCheckbox")) self.verticalLayout.addWidget(self.promptToSaveCheckbox) self.showTrayCheckbox = QtGui.QCheckBox(self.groupBox) self.showTrayCheckbox.setObjectName(_fromUtf8("showTrayCheckbox")) self.verticalLayout.addWidget(self.showTrayCheckbox) self.verticalLayout_4.addWidget(self.groupBox) self.groupBox_2 = QtGui.QGroupBox(Form) self.groupBox_2.setObjectName(_fromUtf8("groupBox_2")) self.verticalLayout_2 = QtGui.QVBoxLayout(self.groupBox_2) self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) self.allowKbNavCheckbox = QtGui.QCheckBox(self.groupBox_2) self.allowKbNavCheckbox.setObjectName(_fromUtf8("allowKbNavCheckbox")) self.verticalLayout_2.addWidget(self.allowKbNavCheckbox) self.sortByUsageCheckbox = QtGui.QCheckBox(self.groupBox_2) self.sortByUsageCheckbox.setObjectName(_fromUtf8("sortByUsageCheckbox")) self.verticalLayout_2.addWidget(self.sortByUsageCheckbox) self.verticalLayout_4.addWidget(self.groupBox_2) self.groupBox_3 = QtGui.QGroupBox(Form) self.groupBox_3.setObjectName(_fromUtf8("groupBox_3")) self.verticalLayout_3 = QtGui.QVBoxLayout(self.groupBox_3) self.verticalLayout_3.setObjectName(_fromUtf8("verticalLayout_3")) self.enableUndoCheckbox = QtGui.QCheckBox(self.groupBox_3) self.enableUndoCheckbox.setObjectName(_fromUtf8("enableUndoCheckbox")) self.verticalLayout_3.addWidget(self.enableUndoCheckbox) self.verticalLayout_4.addWidget(self.groupBox_3) spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.verticalLayout_4.addItem(spacerItem) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): Form.setWindowTitle(kdecore.i18n(_fromUtf8("Form"))) self.groupBox.setTitle(kdecore.i18n(_fromUtf8("Application"))) self.promptToSaveCheckbox.setText(kdecore.i18n(_fromUtf8("Prompt for unsaved changes"))) self.showTrayCheckbox.setText(kdecore.i18n(_fromUtf8("Show a notification icon"))) self.groupBox_2.setTitle(kdecore.i18n(_fromUtf8("Popup Menu"))) self.allowKbNavCheckbox.setText(kdecore.i18n(_fromUtf8("Allow keyboard navigation of popup menu"))) self.sortByUsageCheckbox.setText(kdecore.i18n(_fromUtf8("Sort menu items with most frequently used first"))) self.groupBox_3.setTitle(kdecore.i18n(_fromUtf8("Expansions"))) self.enableUndoCheckbox.setText(kdecore.i18n(_fromUtf8("Enable undo by pressing backspace"))) autokey-0.90.4/src/lib/qtui/__init__.py0000664000175000017500000000000011723375735016724 0ustar chrischrisautokey-0.90.4/src/lib/qtui/scriptpage.py0000664000175000017500000000570411724546455017346 0ustar chrischris#!/usr/bin/env python # coding=UTF-8 # # Generated by pykdeuic4 from scriptpage.ui on Sun Mar 4 11:39:40 2012 # # WARNING! All changes to this file will be lost. from PyKDE4 import kdecore from PyKDE4 import kdeui from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_ScriptPage(object): def setupUi(self, ScriptPage): ScriptPage.setObjectName(_fromUtf8("ScriptPage")) ScriptPage.resize(587, 581) self.verticalLayout_2 = QtGui.QVBoxLayout(ScriptPage) self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2")) self.urlLabel = KUrlLabel(ScriptPage) self.urlLabel.setAlignment(QtCore.Qt.AlignCenter) self.urlLabel.setObjectName(_fromUtf8("urlLabel")) self.verticalLayout_2.addWidget(self.urlLabel) self.scriptCodeEditor = Qsci.QsciScintilla(ScriptPage) self.scriptCodeEditor.setToolTip(_fromUtf8("")) self.scriptCodeEditor.setWhatsThis(_fromUtf8("")) self.scriptCodeEditor.setObjectName(_fromUtf8("scriptCodeEditor")) self.verticalLayout_2.addWidget(self.scriptCodeEditor) self.settingsGroupbox = QtGui.QGroupBox(ScriptPage) self.settingsGroupbox.setObjectName(_fromUtf8("settingsGroupbox")) self.verticalLayout = QtGui.QVBoxLayout(self.settingsGroupbox) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.promptCheckbox = QtGui.QCheckBox(self.settingsGroupbox) self.promptCheckbox.setObjectName(_fromUtf8("promptCheckbox")) self.verticalLayout.addWidget(self.promptCheckbox) self.showInTrayCheckbox = QtGui.QCheckBox(self.settingsGroupbox) self.showInTrayCheckbox.setObjectName(_fromUtf8("showInTrayCheckbox")) self.verticalLayout.addWidget(self.showInTrayCheckbox) self.kseparator = KSeparator(self.settingsGroupbox) self.kseparator.setObjectName(_fromUtf8("kseparator")) self.verticalLayout.addWidget(self.kseparator) self.settingsWidget = SettingsWidget(self.settingsGroupbox) self.settingsWidget.setObjectName(_fromUtf8("settingsWidget")) self.verticalLayout.addWidget(self.settingsWidget) self.verticalLayout_2.addWidget(self.settingsGroupbox) self.retranslateUi(ScriptPage) QtCore.QMetaObject.connectSlotsByName(ScriptPage) def retranslateUi(self, ScriptPage): ScriptPage.setWindowTitle(kdecore.i18n(_fromUtf8("Form"))) self.urlLabel.setTipText(kdecore.i18n(_fromUtf8("Open the script in the default text editor"))) self.settingsGroupbox.setTitle(kdecore.i18n(_fromUtf8("Script Settings"))) self.promptCheckbox.setText(kdecore.i18n(_fromUtf8("Always prompt before running this script"))) self.showInTrayCheckbox.setText(kdecore.i18n(_fromUtf8("Show in notification icon menu"))) from PyQt4 import Qsci from PyKDE4.kdeui import KUrlLabel, KSeparator from configwindow import SettingsWidget autokey-0.90.4/src/lib/qtui/settingswidget.py0000664000175000017500000001013711724546455020245 0ustar chrischris#!/usr/bin/env python # coding=UTF-8 # # Generated by pykdeuic4 from settingswidget.ui on Sun Mar 4 11:39:40 2012 # # WARNING! All changes to this file will be lost. from PyKDE4 import kdecore from PyKDE4 import kdeui from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: _fromUtf8 = lambda s: s class Ui_SettingsWidget(object): def setupUi(self, SettingsWidget): SettingsWidget.setObjectName(_fromUtf8("SettingsWidget")) SettingsWidget.resize(316, 91) self.gridLayout = QtGui.QGridLayout(SettingsWidget) self.gridLayout.setObjectName(_fromUtf8("gridLayout")) self.label = QtGui.QLabel(SettingsWidget) self.label.setObjectName(_fromUtf8("label")) self.gridLayout.addWidget(self.label, 0, 0, 1, 1) self.abbrLabel = QtGui.QLabel(SettingsWidget) self.abbrLabel.setObjectName(_fromUtf8("abbrLabel")) self.gridLayout.addWidget(self.abbrLabel, 0, 1, 1, 1) self.setAbbrButton = KPushButton(SettingsWidget) self.setAbbrButton.setObjectName(_fromUtf8("setAbbrButton")) self.gridLayout.addWidget(self.setAbbrButton, 0, 3, 1, 1) self.clearAbbrButton = KPushButton(SettingsWidget) self.clearAbbrButton.setObjectName(_fromUtf8("clearAbbrButton")) self.gridLayout.addWidget(self.clearAbbrButton, 0, 4, 1, 1) self.label_2 = QtGui.QLabel(SettingsWidget) self.label_2.setObjectName(_fromUtf8("label_2")) self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1) self.hotkeyLabel = QtGui.QLabel(SettingsWidget) self.hotkeyLabel.setObjectName(_fromUtf8("hotkeyLabel")) self.gridLayout.addWidget(self.hotkeyLabel, 1, 1, 1, 1) self.setHotkeyButton = KPushButton(SettingsWidget) self.setHotkeyButton.setObjectName(_fromUtf8("setHotkeyButton")) self.gridLayout.addWidget(self.setHotkeyButton, 1, 3, 1, 1) self.clearHotkeyButton = KPushButton(SettingsWidget) self.clearHotkeyButton.setObjectName(_fromUtf8("clearHotkeyButton")) self.gridLayout.addWidget(self.clearHotkeyButton, 1, 4, 1, 1) self.label_3 = QtGui.QLabel(SettingsWidget) self.label_3.setObjectName(_fromUtf8("label_3")) self.gridLayout.addWidget(self.label_3, 2, 0, 1, 1) self.windowFilterLabel = QtGui.QLabel(SettingsWidget) self.windowFilterLabel.setObjectName(_fromUtf8("windowFilterLabel")) self.gridLayout.addWidget(self.windowFilterLabel, 2, 1, 1, 1) self.setFilterButton = KPushButton(SettingsWidget) self.setFilterButton.setObjectName(_fromUtf8("setFilterButton")) self.gridLayout.addWidget(self.setFilterButton, 2, 3, 1, 1) self.clearFilterButton = KPushButton(SettingsWidget) self.clearFilterButton.setObjectName(_fromUtf8("clearFilterButton")) self.gridLayout.addWidget(self.clearFilterButton, 2, 4, 1, 1) spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.gridLayout.addItem(spacerItem, 0, 2, 1, 1) self.retranslateUi(SettingsWidget) QtCore.QMetaObject.connectSlotsByName(SettingsWidget) def retranslateUi(self, SettingsWidget): SettingsWidget.setWindowTitle(kdecore.i18n(_fromUtf8("Form"))) self.label.setText(kdecore.i18n(_fromUtf8("Abbreviations:"))) self.abbrLabel.setText(kdecore.i18n(_fromUtf8("$abbr"))) self.setAbbrButton.setText(kdecore.i18n(_fromUtf8("Set&"))) self.clearAbbrButton.setText(kdecore.i18n(_fromUtf8("Clear&"))) self.label_2.setText(kdecore.i18n(_fromUtf8("Hotkey:"))) self.hotkeyLabel.setText(kdecore.i18n(_fromUtf8("$hotkey"))) self.setHotkeyButton.setText(kdecore.i18n(_fromUtf8("Set&"))) self.clearHotkeyButton.setText(kdecore.i18n(_fromUtf8("Clear&"))) self.label_3.setText(kdecore.i18n(_fromUtf8("Window Filter:"))) self.windowFilterLabel.setText(kdecore.i18n(_fromUtf8("$filter"))) self.setFilterButton.setText(kdecore.i18n(_fromUtf8("Set&"))) self.clearFilterButton.setText(kdecore.i18n(_fromUtf8("Clear&"))) from PyKDE4.kdeui import KPushButton autokey-0.90.4/src/lib/gtkui/configwindow.py0000664000175000017500000020255011747702637020042 0ustar chrischris# -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import logging, sys, os, webbrowser, re, time #import gtk, Gtk.glade, gtksourceview2, pango from gi.repository import Gtk, Pango, GtkSource, Gdk, Gio #import gettext import locale GETTEXT_DOMAIN = 'autokey' locale.setlocale(locale.LC_ALL, '') #for module in Gtk.glade, gettext: # module.bindtextdomain(GETTEXT_DOMAIN) # module.textdomain(GETTEXT_DOMAIN) from dialogs import * from settingsdialog import SettingsDialog from autokey.configmanager import * from autokey.iomediator import Recorder from autokey import model, common CONFIG_WINDOW_TITLE = "AutoKey" UI_DESCRIPTION_FILE = os.path.join(os.path.dirname(__file__), "data/menus.xml") _logger = logging.getLogger("configwindow") PROBLEM_MSG_PRIMARY = _("Some problems were found") PROBLEM_MSG_SECONDARY = _("%s\n\nYour changes have not been saved.") def get_ui(fileName): builder = Gtk.Builder() uiFile = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data/" + fileName) builder.add_from_file(uiFile) return builder def set_linkbutton(button, path): button.set_sensitive(True) if path.startswith(CONFIG_DEFAULT_FOLDER): text = path.replace(CONFIG_DEFAULT_FOLDER, _("(Default folder)")) else: text = path.replace(os.path.expanduser("~"), "~") button.set_label(text) button.set_uri("file://" + path) label = button.get_child() label.set_ellipsize(Pango.EllipsizeMode.START) class RenameDialog: def __init__(self, parentWindow, oldName, isNew, title=_("Rename '%s'")): builder = get_ui("renamedialog.xml") self.ui = builder.get_object("dialog") builder.connect_signals(self) self.ui.set_transient_for(parentWindow) self.nameEntry = builder.get_object("nameEntry") self.checkButton = builder.get_object("checkButton") self.image = builder.get_object("image") self.nameEntry.set_text(oldName.encode("utf-8")) self.checkButton.set_active(True) if isNew: self.checkButton.hide() self.set_title(title) else: self.set_title(title % oldName) def get_name(self): return self.nameEntry.get_text().decode("utf-8") def get_update_fs(self): return self.checkButton.get_active() def set_image(self, stockId): self.image.set_from_stock(stockId, Gtk.IconSize.DIALOG) def __getattr__(self, attr): # Magic fudge to allow us to pretend to be the ui class we encapsulate return getattr(self.ui, attr) class SettingsWidget: KEY_MAP = HotkeySettingsDialog.KEY_MAP REVERSE_KEY_MAP = HotkeySettingsDialog.REVERSE_KEY_MAP def __init__(self, parentWindow): self.parentWindow = parentWindow builder = get_ui("settingswidget.xml") self.ui = builder.get_object("settingswidget") builder.connect_signals(self) self.abbrDialog = AbbrSettingsDialog(parentWindow.ui, parentWindow.app.configManager, self.on_abbr_response) self.hotkeyDialog = HotkeySettingsDialog(parentWindow.ui, parentWindow.app.configManager, self.on_hotkey_response) self.filterDialog = WindowFilterSettingsDialog(parentWindow.ui, self.on_filter_dialog_response) self.abbrLabel = builder.get_object("abbrLabel") self.clearAbbrButton = builder.get_object("clearAbbrButton") self.hotkeyLabel = builder.get_object("hotkeyLabel") self.clearHotkeyButton = builder.get_object("clearHotkeyButton") self.windowFilterLabel = builder.get_object("windowFilterLabel") self.clearFilterButton = builder.get_object("clearFilterButton") def load(self, item): self.currentItem = item self.abbrDialog.load(self.currentItem) if model.TriggerMode.ABBREVIATION in item.modes: self.abbrLabel.set_text(item.get_abbreviations()) self.clearAbbrButton.set_sensitive(True) self.abbrEnabled = True else: self.abbrLabel.set_text(_("(None configured)")) self.clearAbbrButton.set_sensitive(False) self.abbrEnabled = False self.hotkeyDialog.load(self.currentItem) if model.TriggerMode.HOTKEY in item.modes: self.hotkeyLabel.set_text(item.get_hotkey_string()) self.clearHotkeyButton.set_sensitive(True) self.hotkeyEnabled = True else: self.hotkeyLabel.set_text(_("(None configured)")) self.clearHotkeyButton.set_sensitive(False) self.hotkeyEnabled = False self.filterDialog.load(self.currentItem) self.filterEnabled = False self.clearFilterButton.set_sensitive(False) if item.has_filter() or item.inherits_filter(): self.windowFilterLabel.set_text(item.get_filter_regex()) if not item.inherits_filter(): self.clearFilterButton.set_sensitive(True) self.filterEnabled = True else: self.windowFilterLabel.set_text(_("(None configured)")) def save(self): # Perform hotkey ungrab if model.TriggerMode.HOTKEY in self.currentItem.modes: self.parentWindow.app.hotkey_removed(self.currentItem) self.currentItem.set_modes([]) if self.abbrEnabled: self.abbrDialog.save(self.currentItem) if self.hotkeyEnabled: self.hotkeyDialog.save(self.currentItem) if self.filterEnabled: self.filterDialog.save(self.currentItem) else: self.currentItem.set_window_titles(None) if self.hotkeyEnabled: self.parentWindow.app.hotkey_created(self.currentItem) def set_dirty(self): self.parentWindow.set_dirty(True) def validate(self): # Start by getting all applicable information if self.abbrEnabled: abbreviations = self.abbrDialog.get_abbrs() else: abbreviations = [] if self.hotkeyEnabled: modifiers = self.hotkeyDialog.build_modifiers() key = self.hotkeyDialog.key else: modifiers = [] key = None filterExpression = None if self.filterEnabled: filterExpression = self.filterDialog.get_filter_text() elif self.currentItem.parent is not None: r = self.currentItem.parent.get_applicable_regex(True) if r is not None: filterExpression = r.pattern # Validate ret = [] configManager = self.parentWindow.app.configManager for abbr in abbreviations: unique, conflicting = configManager.check_abbreviation_unique(abbr, filterExpression, self.currentItem) if not unique: msg = _("The abbreviation '%s' is already in use by the %s") % (abbr, str(conflicting)) f = conflicting.get_applicable_regex() if f is not None: msg += _(" for windows matching '%s'.") % f.pattern ret.append(msg) unique, conflicting = configManager.check_hotkey_unique(modifiers, key, filterExpression, self.currentItem) if not unique: msg = _("The hotkey '%s' is already in use by the %s") % (conflicting.get_hotkey_string(), str(conflicting)) f = conflicting.get_applicable_regex() if f is not None: msg += _(" for windows matching '%s'.") % f.pattern ret.append(msg) return ret # ---- Signal handlers def on_setAbbrButton_clicked(self, widget, data=None): self.abbrDialog.reset_focus() self.abbrDialog.show() def on_abbr_response(self, res): if res == Gtk.ResponseType.OK: self.set_dirty() self.abbrEnabled = True self.abbrLabel.set_text(self.abbrDialog.get_abbrs_readable()) self.clearAbbrButton.set_sensitive(True) def on_clearAbbrButton_clicked(self, widget, data=None): self.set_dirty() self.abbrEnabled = False self.clearAbbrButton.set_sensitive(False) self.abbrLabel.set_text(_("(None configured)")) self.abbrDialog.reset() def on_setHotkeyButton_clicked(self, widget, data=None): self.hotkeyDialog.show() def on_hotkey_response(self, res): if res == Gtk.ResponseType.OK: self.set_dirty() self.hotkeyEnabled = True key = self.hotkeyDialog.key modifiers = self.hotkeyDialog.build_modifiers() self.hotkeyLabel.set_text(self.currentItem.get_hotkey_string(key, modifiers)) self.clearHotkeyButton.set_sensitive(True) def on_clearHotkeyButton_clicked(self, widget, data=None): self.set_dirty() self.hotkeyEnabled = False self.clearHotkeyButton.set_sensitive(False) self.hotkeyLabel.set_text(_("(None configured)")) self.hotkeyDialog.reset() def on_setFilterButton_clicked(self, widget, data=None): self.filterDialog.reset_focus() self.filterDialog.show() def on_clearFilterButton_clicked(self, widget, data=None): self.set_dirty() self.filterEnabled = False self.clearFilterButton.set_sensitive(False) if self.currentItem.inherits_filter(): text = self.currentItem.parent.get_child_filter() else: text = _("(None configured)") self.windowFilterLabel.set_text(text) self.filterDialog.reset() def on_filter_dialog_response(self, res): if res == Gtk.ResponseType.OK: self.set_dirty() filterText = self.filterDialog.get_filter_text() if filterText != "": self.filterEnabled = True self.clearFilterButton.set_sensitive(True) self.windowFilterLabel.set_text(filterText) else: self.filterEnabled = False self.clearFilterButton.set_sensitive(False) if self.currentItem.inherits_filter(): text = self.currentItem.parent.get_child_filter() else: text = _("(None configured)") self.windowFilterLabel.set_text(text) def __getattr__(self, attr): # Magic fudge to allow us to pretend to be the ui class we encapsulate return getattr(self.ui, attr) class BlankPage: def __init__(self, parentWindow): self.parentWindow = parentWindow builder = get_ui("blankpage.xml") self.ui = builder.get_object("blankpage") def load(self, theFolder): pass def save(self): pass def set_item_title(self, newTitle): pass def reset(self): pass def validate(self): return True def on_modified(self, widget, data=None): pass def set_dirty(self): self.parentWindow.set_dirty(True) class FolderPage: def __init__(self, parentWindow): self.parentWindow = parentWindow builder = get_ui("folderpage.xml") self.ui = builder.get_object("folderpage") builder.connect_signals(self) self.showInTrayCheckbox = builder.get_object("showInTrayCheckbox") self.linkButton = builder.get_object("linkButton") label = self.linkButton.get_child() label.set_ellipsize(Pango.EllipsizeMode.MIDDLE) vbox = builder.get_object("settingsVbox") self.settingsWidget = SettingsWidget(parentWindow) vbox.pack_start(self.settingsWidget.ui, True, True, 0) def load(self, theFolder): self.currentFolder = theFolder self.showInTrayCheckbox.set_active(theFolder.showInTrayMenu) self.settingsWidget.load(theFolder) if self.is_new_item(): self.linkButton.set_sensitive(False) self.linkButton.set_label(_("(Unsaved)")) else: set_linkbutton(self.linkButton, self.currentFolder.path) def save(self): self.currentFolder.showInTrayMenu = self.showInTrayCheckbox.get_active() self.settingsWidget.save() self.currentFolder.persist() set_linkbutton(self.linkButton, self.currentFolder.path) return not self.currentFolder.path.startswith(CONFIG_DEFAULT_FOLDER) def set_item_title(self, newTitle): self.currentFolder.title = newTitle.decode("utf-8") def rebuild_item_path(self): self.currentFolder.rebuild_path() def is_new_item(self): return self.currentFolder.path is None def reset(self): self.load(self.currentFolder) def validate(self): # Check settings errors = self.settingsWidget.validate() if errors: msg = PROBLEM_MSG_SECONDARY % '\n'.join(errors) dlg = Gtk.MessageDialog(self.parentWindow.ui, Gtk.DialogFlags.MODAL|Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, PROBLEM_MSG_PRIMARY) dlg.format_secondary_text(msg) dlg.run() dlg.destroy() return len(errors) == 0 def on_modified(self, widget, data=None): self.set_dirty() def set_dirty(self): self.parentWindow.set_dirty(True) class ScriptPage: def __init__(self, parentWindow): self.parentWindow = parentWindow builder = get_ui("scriptpage.xml") self.ui = builder.get_object("scriptpage") builder.connect_signals(self) self.buffer = GtkSource.Buffer() self.buffer.connect("changed", self.on_modified) self.editor = GtkSource.View.new_with_buffer(self.buffer) scrolledWindow = builder.get_object("scrolledWindow") scrolledWindow.add(self.editor) # Editor font settings = Gio.Settings.new("org.gnome.desktop.interface") fontDesc = Pango.font_description_from_string(settings.get_string("monospace-font-name")) self.editor.modify_font(fontDesc) self.promptCheckbox = builder.get_object("promptCheckbox") self.showInTrayCheckbox = builder.get_object("showInTrayCheckbox") self.linkButton = builder.get_object("linkButton") label = self.linkButton.get_child() label.set_ellipsize(Pango.EllipsizeMode.MIDDLE) vbox = builder.get_object("settingsVbox") self.settingsWidget = SettingsWidget(parentWindow) vbox.pack_start(self.settingsWidget.ui, False, False, 0) # Configure script editor self.__m = GtkSource.LanguageManager() self.__sm = GtkSource.StyleSchemeManager() self.buffer.set_language(self.__m.get_language("python")) self.buffer.set_style_scheme(self.__sm.get_scheme("classic")) self.editor.set_auto_indent(True) self.editor.set_smart_home_end(True) self.editor.set_insert_spaces_instead_of_tabs(True) self.editor.set_tab_width(4) self.ui.show_all() def load(self, theScript): self.currentItem = theScript self.buffer.begin_not_undoable_action() self.buffer.set_text(theScript.code.encode("utf-8")) self.buffer.end_not_undoable_action() self.buffer.place_cursor(self.buffer.get_start_iter()) self.promptCheckbox.set_active(theScript.prompt) self.showInTrayCheckbox.set_active(theScript.showInTrayMenu) self.settingsWidget.load(theScript) if self.is_new_item(): self.linkButton.set_sensitive(False) self.linkButton.set_label(_("(Unsaved)")) else: set_linkbutton(self.linkButton, self.currentItem.path) def save(self): self.currentItem.code = self.buffer.get_text(self.buffer.get_start_iter(), self.buffer.get_end_iter(), False).decode("utf-8") self.currentItem.prompt = self.promptCheckbox.get_active() self.currentItem.showInTrayMenu = self.showInTrayCheckbox.get_active() self.settingsWidget.save() self.currentItem.persist() set_linkbutton(self.linkButton, self.currentItem.path) return False def set_item_title(self, newTitle): self.currentItem.description = newTitle.decode("utf-8") def rebuild_item_path(self): self.currentItem.rebuild_path() def is_new_item(self): return self.currentItem.path is None def reset(self): self.load(self.currentItem) self.parentWindow.set_undo_available(False) self.parentWindow.set_redo_available(False) def validate(self): errors = [] # Check script code text = self.buffer.get_text(self.buffer.get_start_iter(), self.buffer.get_end_iter(), False) if EMPTY_FIELD_REGEX.match(text): errors.append(_("The script code can't be empty")) # Check settings errors += self.settingsWidget.validate() if errors: msg = PROBLEM_MSG_SECONDARY % '\n'.join(errors) dlg = Gtk.MessageDialog(self.parentWindow.ui, Gtk.DialogFlags.MODAL|Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, PROBLEM_MSG_PRIMARY) dlg.format_secondary_text(msg) dlg.run() dlg.destroy() return len(errors) == 0 def record_keystrokes(self, isActive): if isActive: self.recorder = Recorder(self) dlg = RecordDialog(self.ui, self.on_rec_response) dlg.run() else: self.recorder.stop() def on_rec_response(self, response, recKb, recMouse, delay): if response == Gtk.ResponseType.OK: self.recorder.set_record_keyboard(recKb) self.recorder.set_record_mouse(recMouse) self.recorder.start(delay) elif response == Gtk.ResponseType.CANCEL: self.parentWindow.record_stopped() def cancel_record(self): self.recorder.stop() def start_record(self): self.buffer.insert(self.buffer.get_end_iter(), "\n") def start_key_sequence(self): self.buffer.insert(self.buffer.get_end_iter(), "keyboard.send_keys(\"") def end_key_sequence(self): self.buffer.insert(self.buffer.get_end_iter(), "\")\n") def append_key(self, key): #line, pos = self.buffer.getCursorPosition() self.buffer.insert(self.buffer.get_end_iter(), key) #self.scriptCodeEditor.setCursorPosition(line, pos + len(key)) def append_hotkey(self, key, modifiers): #line, pos = self.scriptCodeEditor.getCursorPosition() keyString = self.currentItem.get_hotkey_string(key, modifiers) self.buffer.insert(self.buffer.get_end_iter(), keyString) #self.scriptCodeEditor.setCursorPosition(line, pos + len(keyString)) def append_mouseclick(self, xCoord, yCoord, button, windowTitle): self.buffer.insert(self.buffer.get_end_iter(), "mouse.click_relative(%d, %d, %d) # %s\n" % (xCoord, yCoord, int(button), windowTitle)) def undo(self): self.buffer.undo() self.parentWindow.set_undo_available(self.buffer.can_undo()) self.parentWindow.set_redo_available(self.buffer.can_redo()) def redo(self): self.buffer.redo() self.parentWindow.set_undo_available(self.buffer.can_undo()) self.parentWindow.set_redo_available(self.buffer.can_redo()) def on_modified(self, widget, data=None): self.set_dirty() self.parentWindow.set_undo_available(self.buffer.can_undo()) self.parentWindow.set_redo_available(self.buffer.can_redo()) def set_dirty(self): self.parentWindow.set_dirty(True) class PhrasePage(ScriptPage): def __init__(self, parentWindow): self.parentWindow = parentWindow builder = get_ui("phrasepage.xml") self.ui = builder.get_object("phrasepage") builder.connect_signals(self) self.buffer = GtkSource.Buffer() self.buffer.connect("changed", self.on_modified) self.editor = GtkSource.View.new_with_buffer(self.buffer) scrolledWindow = builder.get_object("scrolledWindow") scrolledWindow.add(self.editor) self.promptCheckbox = builder.get_object("promptCheckbox") self.showInTrayCheckbox = builder.get_object("showInTrayCheckbox") self.sendModeCombo = Gtk.ComboBoxText.new() self.sendModeCombo.connect("changed", self.on_modified) sendModeHbox = builder.get_object("sendModeHbox") sendModeHbox.pack_start(self.sendModeCombo, False, False, 0) self.linkButton = builder.get_object("linkButton") vbox = builder.get_object("settingsVbox") self.settingsWidget = SettingsWidget(parentWindow) vbox.pack_start(self.settingsWidget.ui, False, False, 0) # Populate combo l = model.SEND_MODES.keys() l.sort() for val in l: self.sendModeCombo.append_text(val) # Configure script editor #self.__m = GtkSource.LanguageManager() self.__sm = GtkSource.StyleSchemeManager() self.buffer.set_language(None) self.buffer.set_style_scheme(self.__sm.get_scheme("kate")) self.buffer.set_highlight_matching_brackets(False) self.editor.set_auto_indent(False) self.editor.set_smart_home_end(False) self.editor.set_insert_spaces_instead_of_tabs(True) self.editor.set_tab_width(4) self.ui.show_all() def insert_text(self, text): self.buffer.insert_at_cursor(text.encode("utf-8")) def load(self, thePhrase): self.currentItem = thePhrase self.buffer.begin_not_undoable_action() self.buffer.set_text(thePhrase.phrase.encode("utf-8")) self.buffer.end_not_undoable_action() self.buffer.place_cursor(self.buffer.get_start_iter()) self.promptCheckbox.set_active(thePhrase.prompt) self.showInTrayCheckbox.set_active(thePhrase.showInTrayMenu) self.settingsWidget.load(thePhrase) if self.is_new_item(): self.linkButton.set_sensitive(False) self.linkButton.set_label(_("(Unsaved)")) else: set_linkbutton(self.linkButton, self.currentItem.path) l = model.SEND_MODES.keys() l.sort() for k, v in model.SEND_MODES.iteritems(): if v == thePhrase.sendMode: self.sendModeCombo.set_active(l.index(k)) break def save(self): self.currentItem.phrase = self.buffer.get_text(self.buffer.get_start_iter(), self.buffer.get_end_iter(), False).decode("utf-8") self.currentItem.prompt = self.promptCheckbox.get_active() self.currentItem.showInTrayMenu = self.showInTrayCheckbox.get_active() self.currentItem.sendMode = model.SEND_MODES[self.sendModeCombo.get_active_text()] self.settingsWidget.save() self.currentItem.persist() set_linkbutton(self.linkButton, self.currentItem.path) return False def validate(self): errors = [] # Check phrase content text = self.buffer.get_text(self.buffer.get_start_iter(), self.buffer.get_end_iter(), False).decode("utf-8") if EMPTY_FIELD_REGEX.match(text): errors.append(_("The phrase content can't be empty")) # Check settings errors += self.settingsWidget.validate() if errors: msg = PROBLEM_MSG_SECONDARY % '\n'.join(errors) dlg = Gtk.MessageDialog(self.parentWindow.ui, Gtk.DialogFlags.MODAL|Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, PROBLEM_MSG_PRIMARY) dlg.format_secondary_text(msg) dlg.run() dlg.destroy() return len(errors) == 0 def record_keystrokes(self, isActive): if isActive: msg = _("AutoKey will now take exclusive use of the keyboard.\n\nClick the mouse anywhere to release the keyboard when you are done.") dlg = Gtk.MessageDialog(self.parentWindow.ui, Gtk.DialogFlags.MODAL, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, msg) dlg.set_title(_("Record Keystrokes")) dlg.run() dlg.destroy() self.editor.set_sensitive(False) self.recorder = Recorder(self) self.recorder.set_record_keyboard(True) self.recorder.set_record_mouse(True) self.recorder.start_withgrab() else: self.recorder.stop() self.editor.set_sensitive(True) def start_record(self): pass def start_key_sequence(self): pass def end_key_sequence(self): pass def append_key(self, key): #line, pos = self.buffer.getCursorPosition() self.buffer.insert(self.buffer.get_end_iter(), key) #self.scriptCodeEditor.setCursorPosition(line, pos + len(key)) def append_hotkey(self, key, modifiers): #line, pos = self.scriptCodeEditor.getCursorPosition() keyString = self.currentItem.get_hotkey_string(key, modifiers) self.buffer.insert(self.buffer.get_end_iter(), keyString) #self.scriptCodeEditor.setCursorPosition(line, pos + len(keyString)) def append_mouseclick(self, xCoord, yCoord, button, windowTitle): self.cancel_record() self.parentWindow.record_stopped() def cancel_record(self): self.recorder.stop_withgrab() self.editor.set_sensitive(True) class ConfigWindow: def __init__(self, app): self.app = app self.cutCopiedItems = [] self.__warnedOfChanges = False builder = get_ui("mainwindow.xml") self.ui = builder.get_object("mainwindow") self.ui.set_title(CONFIG_WINDOW_TITLE) # Menus and Actions self.uiManager = Gtk.UIManager() self.add_accel_group(self.uiManager.get_accel_group()) # Menu Bar actionGroup = Gtk.ActionGroup("menu") actions = [ ("File", None, _("_File")), ("create", None, _("New")), ("new-top-folder", "folder-new", _("_Folder"), "", _("Create a new top-level folder"), self.on_new_topfolder), ("new-folder", "folder-new", _("Subf_older"), "", _("Create a new folder in the current folder"), self.on_new_folder), ("new-phrase", "text-x-generic", _("_Phrase"), "n", _("Create a new phrase in the current folder"), self.on_new_phrase), ("new-script", "text-x-python", _("Scrip_t"), "n", _("Create a new script in the current folder"), self.on_new_script), ("save", Gtk.STOCK_SAVE, _("_Save"), None, _("Save changes to current item"), self.on_save), ("revert", Gtk.STOCK_REVERT_TO_SAVED, _("_Revert"), None, _("Drop all unsaved changes to current item"), self.on_revert), ("close-window", Gtk.STOCK_CLOSE, _("_Close window"), None, _("Close the configuration window"), self.on_close), ("quit", Gtk.STOCK_QUIT, _("_Quit"), None, _("Completely exit AutoKey"), self.on_quit), ("Edit", None, _("_Edit")), ("cut-item", Gtk.STOCK_CUT, _("Cu_t Item"), "", _("Cut the selected item"), self.on_cut_item), ("copy-item", Gtk.STOCK_COPY, _("_Copy Item"), "", _("Copy the selected item"), self.on_copy_item), ("paste-item", Gtk.STOCK_PASTE, _("_Paste Item"), "", _("Paste the last cut/copied item"), self.on_paste_item), ("clone-item", Gtk.STOCK_COPY, _("C_lone Item"), "c", _("Clone the selected item"), self.on_clone_item), ("delete-item", Gtk.STOCK_DELETE, _("_Delete Item"), "d", _("Delete the selected item"), self.on_delete_item), ("rename", None, _("_Rename"), "F2", _("Rename the selected item"), self.on_rename), ("undo", Gtk.STOCK_UNDO, _("_Undo"), "z", _("Undo the last edit"), self.on_undo), ("redo", Gtk.STOCK_REDO, _("_Redo"), "z", _("Redo the last undone edit"), self.on_redo), ("insert-macro", None, _("_Insert Macro"), None, _("Insert a phrase macro"), None), ("preferences", Gtk.STOCK_PREFERENCES, _("_Preferences"), "", _("Additional options"), self.on_advanced_settings), ("Tools", None, _("_Tools")), ("script-error", Gtk.STOCK_DIALOG_ERROR, _("Vie_w script error"), None, _("View script error information"), self.on_show_error), ("run", Gtk.STOCK_MEDIA_PLAY, _("_Run current script"), None, _("Run the currently selected script"), self.on_run_script), #("Settings", None, _("_Settings"), None, None, None), #("advanced", Gtk.STOCK_PREFERENCES, _("_Advanced Settings"), "", _("Advanced configuration options"), self.on_advanced_settings), ("Help", None, _("_Help")), ("faq", None, _("_F.A.Q."), None, _("Display Frequently Asked Questions"), self.on_show_faq), ("help", Gtk.STOCK_HELP, _("Online _Help"), None, _("Display Online Help"), self.on_show_help), ("api", None, _("_Scripting Help"), None, _("Display Scripting API"), self.on_show_api), ("donate", Gtk.STOCK_YES, _("Donate"), "", _("Make a Donation"), self.on_donate), ("report-bug", None, _("Report a Bug"), "", _("Report a Bug"), self.on_report_bug), ("about", Gtk.STOCK_ABOUT, _("About AutoKey"), None, _("Show program information"), self.on_show_about) ] actionGroup.add_actions(actions) toggleActions = [ ("toolbar", None, _("_Show Toolbar"), None, _("Show/hide the toolbar"), self.on_toggle_toolbar), ("record", Gtk.STOCK_MEDIA_RECORD, _("R_ecord keyboard/mouse"), None, _("Record keyboard/mouse actions"), self.on_record_keystrokes), ] actionGroup.add_toggle_actions(toggleActions) self.uiManager.insert_action_group(actionGroup, 0) self.uiManager.add_ui_from_file(UI_DESCRIPTION_FILE) self.vbox = builder.get_object("vbox") self.vbox.pack_end(self.uiManager.get_widget("/MenuBar"), False, False, 0) # Macro menu menu = self.app.service.phraseRunner.macroManager.get_menu(self.on_insert_macro) self.uiManager.get_widget("/MenuBar/Edit/insert-macro").set_submenu(menu) # Toolbar 'create' button create = Gtk.MenuToolButton.new_from_stock(Gtk.STOCK_NEW) create.show() create.set_is_important(True) create.connect("clicked", self.on_new_clicked) menu = self.uiManager.get_widget('/NewDropdown') create.set_menu(menu) toolbar = self.uiManager.get_widget('/Toolbar') toolbar.insert(create, 0) self.uiManager.get_action("/MenuBar/Tools/toolbar").set_active(ConfigManager.SETTINGS[SHOW_TOOLBAR]) toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_PRIMARY_TOOLBAR) self.treeView = builder.get_object("treeWidget") self.__initTreeWidget() self.stack = builder.get_object("stack") self.__initStack() self.hpaned = builder.get_object("hpaned") self.uiManager.get_widget("/Toolbar/save").set_is_important(True) self.uiManager.get_widget("/Toolbar/undo").set_is_important(True) builder.connect_signals(self) rootIter = self.treeView.get_model().get_iter_first() if rootIter is not None: self.treeView.get_selection().select_iter(rootIter) self.on_tree_selection_changed(self.treeView) self.treeView.columns_autosize() width, height = ConfigManager.SETTINGS[WINDOW_DEFAULT_SIZE] self.set_default_size(width, height) self.hpaned.set_position(ConfigManager.SETTINGS[HPANE_POSITION]) def __addToolbar(self): toolbar = self.uiManager.get_widget('/Toolbar') self.vbox.pack_end(toolbar, False, False, 0) self.vbox.reorder_child(toolbar, 1) def record_stopped(self): self.uiManager.get_widget("/MenuBar/Tools/record").set_active(False) def cancel_record(self): if self.uiManager.get_widget("/MenuBar/Tools/record").get_active(): self.record_stopped() self.__getCurrentPage().cancel_record() def save_completed(self, persistGlobal): self.uiManager.get_action("/MenuBar/File/save").set_sensitive(False) self.app.config_altered(persistGlobal) def set_dirty(self, dirty): self.dirty = dirty self.uiManager.get_action("/MenuBar/File/save").set_sensitive(dirty) self.uiManager.get_action("/MenuBar/File/revert").set_sensitive(dirty) def config_modified(self): if not self.__warnedOfChanges: Gdk.threads_enter() msg = _("Changes made in other programs will not be displayed until you\ close and reopen the AutoKey window.\nThis message is only shown once per session.") dlg = Gtk.MessageDialog(self.ui, type=Gtk.MessageType.QUESTION, buttons=Gtk.ButtonsType.OK, message_format= _("Configuration has been changed on disk.")) dlg.format_secondary_text(msg) dlg.run() dlg.destroy() Gdk.threads_leave() self.__warnedOfChanges = True def update_actions(self, items, changed): if len(items) == 0: canCreate = False canCopy = False canRecord = False canMacro = False canPlay = False enableAny = False else: canCreate = isinstance(items[0], model.Folder) and len(items) == 1 canCopy = True canRecord = (not isinstance(items[0], model.Folder)) and len(items) == 1 canMacro = isinstance(items[0], model.Phrase) and len(items) == 1 canPlay = isinstance(items[0], model.Script) and len(items) == 1 enableAny = True for item in items: if isinstance(item, model.Folder): canCopy = False break self.uiManager.get_action("/MenuBar/Edit/copy-item").set_sensitive(canCopy) self.uiManager.get_action("/MenuBar/Edit/cut-item").set_sensitive(enableAny) self.uiManager.get_action("/MenuBar/Edit/clone-item").set_sensitive(canCopy) self.uiManager.get_action("/MenuBar/Edit/paste-item").set_sensitive(len(self.cutCopiedItems) > 0) self.uiManager.get_action("/MenuBar/Edit/delete-item").set_sensitive(enableAny) self.uiManager.get_action("/MenuBar/Edit/rename").set_sensitive(enableAny) self.uiManager.get_action("/MenuBar/Edit/insert-macro").set_sensitive(canMacro) self.uiManager.get_action("/MenuBar/Tools/record").set_sensitive(canRecord) self.uiManager.get_action("/MenuBar/Tools/run").set_sensitive(canPlay) if changed: self.uiManager.get_action("/MenuBar/File/save").set_sensitive(False) self.uiManager.get_action("/MenuBar/Edit/undo").set_sensitive(False) self.uiManager.get_action("/MenuBar/Edit/redo").set_sensitive(False) def set_undo_available(self, state): self.uiManager.get_action("/MenuBar/Edit/undo").set_sensitive(state) def set_redo_available(self, state): self.uiManager.get_action("/MenuBar/Edit/redo").set_sensitive(state) def refresh_tree(self): model, selectedPaths = self.treeView.get_selection().get_selected_rows() for path in selectedPaths: model.update_item(model[path].iter, self.__getTreeSelection()) # ---- Signal handlers ---- def on_new_clicked(self, widget, data=None): widget.get_menu().popup(None, None, None, None, 1, Gtk.get_current_event_time()) def on_save(self, widget, data=None): if self.__getCurrentPage().validate(): self.app.monitor.suspend() persistGlobal = self.__getCurrentPage().save() self.save_completed(persistGlobal) self.set_dirty(False) self.refresh_tree() self.app.monitor.unsuspend() return False return True def on_revert(self, widget, data=None): self.__getCurrentPage().reset() self.set_dirty(False) self.cancel_record() def queryClose(self): if self.dirty: return self.promptToSave() return False def on_close(self, widget, data=None): ConfigManager.SETTINGS[WINDOW_DEFAULT_SIZE] = self.get_size() ConfigManager.SETTINGS[HPANE_POSITION] = self.hpaned.get_position() self.cancel_record() if self.queryClose(): return True else: self.hide() self.destroy() self.app.configWindow = None self.app.config_altered(True) def on_quit(self, widget, data=None): #if not self.queryClose(): ConfigManager.SETTINGS[WINDOW_DEFAULT_SIZE] = self.get_size() ConfigManager.SETTINGS[HPANE_POSITION] = self.hpaned.get_position() self.app.shutdown() # File Menu def on_new_topfolder(self, widget, data=None): dlg = Gtk.FileChooserDialog(_("Create New Folder"), self.ui) dlg.set_action(Gtk.FileChooserAction.CREATE_FOLDER) dlg.set_local_only(True) dlg.add_buttons(_("Use Default"), Gtk.ResponseType.NONE, Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK, Gtk.ResponseType.OK) response = dlg.run() if response == Gtk.ResponseType.OK: path = dlg.get_filename() self.__createFolder(os.path.basename(path), None, path) self.app.monitor.add_watch(path) dlg.destroy() self.app.config_altered(True) elif response == Gtk.ResponseType.NONE: dlg.destroy() name = self.__getNewItemName("Folder") self.__createFolder(name, None) self.app.config_altered(True) else: dlg.destroy() def __getRealParent(self, parentIter): theModel = self.treeView.get_model() parentModelItem = theModel.get_value(parentIter, AkTreeModel.OBJECT_COLUMN) if not isinstance(parentModelItem, model.Folder): return theModel.iter_parent(parentIter) return parentIter def on_new_folder(self, widget, data=None): name = self.__getNewItemName("Folder") if name is not None: theModel, selectedPaths = self.treeView.get_selection().get_selected_rows() parentIter = self.__getRealParent(theModel[selectedPaths[0]].iter) self.__createFolder(name, parentIter) self.app.config_altered(False) def __createFolder(self, title, parentIter, path=None): self.app.monitor.suspend() theModel = self.treeView.get_model() newFolder = model.Folder(title, path=path) newIter = theModel.append_item(newFolder, parentIter) newFolder.persist() self.app.monitor.unsuspend() self.treeView.expand_to_path(theModel.get_path(newIter)) self.treeView.get_selection().unselect_all() self.treeView.get_selection().select_iter(newIter) self.on_tree_selection_changed(self.treeView) def __getNewItemName(self, itemType): dlg = RenameDialog(self.ui, "New %s" % itemType, True, _("Create New %s") % itemType) dlg.set_image(Gtk.STOCK_NEW) if dlg.run() == 1: newText = dlg.get_name() if validate(not EMPTY_FIELD_REGEX.match(newText), _("The name can't be empty"), None, self.ui): dlg.destroy() return newText else: dlg.destroy() return None dlg.destroy() return None def on_new_phrase(self, widget, data=None): name = self.__getNewItemName("Phrase") if name is not None: self.app.monitor.suspend() theModel, selectedPaths = self.treeView.get_selection().get_selected_rows() parentIter = self.__getRealParent(theModel[selectedPaths[0]].iter) newPhrase = model.Phrase(name, "Enter phrase contents") newIter = theModel.append_item(newPhrase, parentIter) newPhrase.persist() self.app.monitor.unsuspend() self.treeView.expand_to_path(theModel.get_path(newIter)) self.treeView.get_selection().unselect_all() self.treeView.get_selection().select_iter(newIter) self.on_tree_selection_changed(self.treeView) #self.on_rename(self.treeView) def on_new_script(self, widget, data=None): name = self.__getNewItemName("Script") if name is not None: self.app.monitor.suspend() theModel, selectedPaths = self.treeView.get_selection().get_selected_rows() parentIter = self.__getRealParent(theModel[selectedPaths[0]].iter) newScript = model.Script(name, "# Enter script code") newIter = theModel.append_item(newScript, parentIter) newScript.persist() self.app.monitor.unsuspend() self.treeView.expand_to_path(theModel.get_path(newIter)) self.treeView.get_selection().unselect_all() self.treeView.get_selection().select_iter(newIter) self.on_tree_selection_changed(self.treeView) # self.on_rename(self.treeView) # Edit Menu def on_cut_item(self, widget, data=None): self.cutCopiedItems = self.__getTreeSelection() selection = self.treeView.get_selection() model, selectedPaths = selection.get_selected_rows() refs = [] for path in selectedPaths: refs.append(Gtk.TreeRowReference(model, path)) for ref in refs: if ref.valid(): self.__removeItem(model, model[ref.get_path()].iter) if len(selectedPaths) > 1: self.treeView.get_selection().unselect_all() self.treeView.get_selection().select_iter(model.get_iter_first()) self.on_tree_selection_changed(self.treeView) self.app.config_altered(True) def on_copy_item(self, widget, data=None): sourceObjects = self.__getTreeSelection() for source in sourceObjects: if isinstance(source, model.Phrase): newObj = model.Phrase('', '') else: newObj = model.Script('', '') newObj.copy(source) self.cutCopiedItems.append(newObj) def on_paste_item(self, widget, data=None): theModel, selectedPaths = self.treeView.get_selection().get_selected_rows() parentIter = self.__getRealParent(theModel[selectedPaths[0]].iter) self.app.monitor.suspend() newIters = [] for item in self.cutCopiedItems: newIter = theModel.append_item(item, parentIter) if isinstance(item, model.Folder): theModel.populate_store(newIter, item) newIters.append(newIter) item.path = None item.persist() self.app.monitor.unsuspend() self.treeView.expand_to_path(theModel.get_path(newIters[-1])) self.treeView.get_selection().unselect_all() self.treeView.get_selection().select_iter(newIters[0]) self.cutCopiedItems = [] self.on_tree_selection_changed(self.treeView) for iter in newIters: self.treeView.get_selection().select_iter(iter) self.app.config_altered(True) def on_clone_item(self, widget, data=None): source = self.__getTreeSelection()[0] theModel, selectedPaths = self.treeView.get_selection().get_selected_rows() sourceIter = theModel[selectedPaths[0]].iter parentIter = theModel.iter_parent(sourceIter) self.app.monitor.suspend() if isinstance(source, model.Phrase): newObj = model.Phrase('', '') else: newObj = model.Script('', '') newObj.copy(source) newObj.persist() self.app.monitor.unsuspend() newIter = theModel.append_item(newObj, parentIter) self.app.config_altered(False) def on_delete_item(self, widget, data=None): selection = self.treeView.get_selection() theModel, selectedPaths = selection.get_selected_rows() refs = [] for path in selectedPaths: refs.append(Gtk.TreeRowReference.new(theModel, path)) modified = False if len(refs) == 1: item = theModel[refs[0].get_path()].iter modelItem = theModel.get_value(item, AkTreeModel.OBJECT_COLUMN) if isinstance(modelItem, model.Folder): msg = _("Are you sure you want to delete the %s and all the items in it?") % str(modelItem) else: msg = _("Are you sure you want to delete the %s?") % str(modelItem) else: msg = _("Are you sure you want to delete the %d selected items?") % len(refs) dlg = Gtk.MessageDialog(self.ui, Gtk.DialogFlags.MODAL, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, msg) dlg.set_title(_("Delete")) if dlg.run() == Gtk.ResponseType.YES: self.app.monitor.suspend() for ref in refs: if ref.valid(): item = theModel[ref.get_path()].iter modelItem = theModel.get_value(item, AkTreeModel.OBJECT_COLUMN) self.__removeItem(theModel, item) modified = True self.app.monitor.unsuspend() dlg.destroy() if modified: if len(selectedPaths) > 1: self.treeView.get_selection().unselect_all() self.treeView.get_selection().select_iter(theModel.get_iter_first()) self.on_tree_selection_changed(self.treeView) self.app.config_altered(True) def __removeItem(self, model, item): #selection = self.treeView.get_selection() #model, selectedPaths = selection.get_selected_rows() #parentIter = model.iter_parent(model[selectedPaths[0]].iter) parentIter = model.iter_parent(item) nextIter = model.iter_next(item) data = model.get_value(item, AkTreeModel.OBJECT_COLUMN) self.__deleteHotkeys(data) model.remove_item(item) if nextIter is not None: self.treeView.get_selection().select_iter(nextIter) elif parentIter is not None: self.treeView.get_selection().select_iter(parentIter) elif model.iter_n_children(None) > 0: selectIter = model.iter_nth_child(None, model.iter_n_children(None) - 1) self.treeView.get_selection().select_iter(selectIter) self.on_tree_selection_changed(self.treeView) def __deleteHotkeys(self, theItem): if model.TriggerMode.HOTKEY in theItem.modes: self.app.hotkey_removed(theItem) if isinstance(theItem, model.Folder): for subFolder in theItem.folders: self.__deleteHotkeys(subFolder) for item in theItem.items: if model.TriggerMode.HOTKEY in item.modes: self.app.hotkey_removed(item) def on_undo(self, widget, data=None): self.__getCurrentPage().undo() def on_redo(self, widget, data=None): self.__getCurrentPage().redo() def on_insert_macro(self, widget, macro): token = macro.get_token() self.phrasePage.insert_text(token) def on_advanced_settings(self, widget, data=None): s = SettingsDialog(self.ui, self.app.configManager) s.show() def on_record_keystrokes(self, widget, data=None): self.__getCurrentPage().record_keystrokes(widget.get_active()) # Tools Menu def on_toggle_toolbar(self, widget, data=None): if widget.get_active(): self.__addToolbar() else: self.vbox.remove(self.uiManager.get_widget('/Toolbar')) ConfigManager.SETTINGS[SHOW_TOOLBAR] = widget.get_active() def on_show_error(self, widget, data=None): self.app.show_script_error(self.ui) def on_run_script(self, widget, data=None): t = threading.Thread(target=self.__runScript) t.start() def __runScript(self): script = self.__getTreeSelection()[0] time.sleep(2) self.app.service.scriptRunner.execute(script) # Help Menu def on_show_faq(self, widget, data=None): webbrowser.open(common.FAQ_URL, False, True) def on_show_help(self, widget, data=None): webbrowser.open(common.HELP_URL, False, True) def on_show_api(self, widget, data=None): webbrowser.open(common.API_URL, False, True) def on_donate(self, widget, data=None): webbrowser.open(common.DONATE_URL, False, True) def on_report_bug(self, widget, data=None): webbrowser.open(common.BUG_URL, False, True) def on_show_about(self, widget, data=None): dlg = Gtk.AboutDialog() dlg.set_name("AutoKey") dlg.set_comments(_("A desktop automation utility for Linux and X11.")) dlg.set_version(common.VERSION) p = Gtk.IconTheme.get_default().load_icon(common.ICON_FILE, 100, 0) dlg.set_logo(p) dlg.set_website(common.HOMEPAGE) dlg.set_authors(["Chris Dekter (Developer) ", "Sam Peterson (Original developer) "]) dlg.set_transient_for(self.ui) dlg.run() dlg.destroy() # Tree widget def on_rename(self, widget, data=None): selection = self.treeView.get_selection() theModel, selectedPaths = selection.get_selected_rows() selection.unselect_all() self.treeView.set_cursor(selectedPaths[0], self.treeView.get_column(0), False) selectedObject = self.__getTreeSelection()[0] if isinstance(selectedObject, model.Folder): oldName = selectedObject.title else: oldName = selectedObject.description dlg = RenameDialog(self.ui, oldName, False) dlg.set_image(Gtk.STOCK_EDIT) if dlg.run() == 1: newText = dlg.get_name() if validate(not EMPTY_FIELD_REGEX.match(newText), _("The name can't be empty"), None, self.ui): self.__getCurrentPage().set_item_title(newText) self.app.monitor.suspend() if dlg.get_update_fs(): self.__getCurrentPage().rebuild_item_path() persistGlobal = self.__getCurrentPage().save() self.refresh_tree() self.app.monitor.unsuspend() self.app.config_altered(persistGlobal) dlg.destroy() def on_treeWidget_row_activated(self, widget, path, viewColumn, data=None): if widget.row_expanded(path): widget.collapse_row(path) else: widget.expand_row(path, False) def on_treeWidget_row_collapsed(self, widget, tIter, path, data=None): widget.columns_autosize() def on_treeview_buttonpress(self, widget, event, data=None): return self.dirty def on_treeview_buttonrelease(self, widget, event, data=None): if self.promptToSave(): # True result indicates user selected Cancel. Stop event propagation return True else: x = int(event.x) y = int(event.y) time = event.time pthinfo = widget.get_path_at_pos(x, y) if pthinfo is not None: path, col, cellx, celly = pthinfo currentPath, currentCol = widget.get_cursor() if currentPath != path: widget.set_cursor(path, col, 0) if event.button == 3: self.__popupMenu(event) return False def on_drag_begin(self, *args): selection = self.treeView.get_selection() theModel, self.__sourceRows = selection.get_selected_rows() self.__sourceObjects = self.__getTreeSelection() def on_tree_selection_changed(self, widget, data=None): selectedObjects = self.__getTreeSelection() if len(selectedObjects) == 0: self.stack.set_current_page(0) self.set_dirty(False) self.cancel_record() self.update_actions(selectedObjects, True) self.selectedObject = None elif len(selectedObjects) == 1: selectedObject = selectedObjects[0] if isinstance(selectedObject, model.Folder): self.stack.set_current_page(1) self.folderPage.load(selectedObject) elif isinstance(selectedObject, model.Phrase): self.stack.set_current_page(2) self.phrasePage.load(selectedObject) else: self.stack.set_current_page(3) self.scriptPage.load(selectedObject) self.set_dirty(False) self.cancel_record() self.update_actions(selectedObjects, True) self.selectedObject = selectedObject else: self.update_actions(selectedObjects, False) def on_drag_data_received(self, treeview, context, x, y, selection, info, etime): selection = self.treeView.get_selection() theModel, sourcePaths = selection.get_selected_rows() drop_info = treeview.get_dest_row_at_pos(x, y) if drop_info: path, position = drop_info targetIter = theModel.get_iter(path) else: targetIter = None #targetModelItem = theModel.get_value(targetIter, AkTreeModel.OBJECT_COLUMN) self.app.monitor.suspend() for path in self.__sourceRows: self.__removeItem(theModel, theModel[path].iter) newIters = [] for item in self.__sourceObjects: newIter = theModel.append_item(item, targetIter) if isinstance(item, model.Folder): theModel.populate_store(newIter, item) self.__dropRecurseUpdate(item) else: item.path = None item.persist() newIters.append(newIter) self.app.monitor.unsuspend() self.treeView.expand_to_path(theModel.get_path(newIters[-1])) selection.unselect_all() for iter in newIters: selection.select_iter(iter) self.on_tree_selection_changed(self.treeView) self.app.config_altered(True) def __dropRecurseUpdate(self, folder): folder.path = None folder.persist() for subfolder in folder.folders: self.__dropRecurseUpdate(subfolder) for child in folder.items: child.path = None child.persist() def on_drag_drop(self, widget, drag_context, x, y, timestamp): drop_info = widget.get_dest_row_at_pos(x, y) if drop_info: selection = widget.get_selection() theModel, sourcePaths = selection.get_selected_rows() path, position = drop_info if position not in (Gtk.TreeViewDropPosition.INTO_OR_BEFORE, Gtk.TreeViewDropPosition.INTO_OR_AFTER): return True targetIter = theModel.get_iter(path) targetModelItem = theModel.get_value(targetIter, AkTreeModel.OBJECT_COLUMN) if isinstance(targetModelItem, model.Folder): # prevent dropping a folder onto itself return path in self.__sourceRows elif targetModelItem is None: # Target is top level for item in self.__sourceObjects: if not isinstance(item, model.Folder): # drop not permitted for top level because not folder return True # prevent dropping a folder onto itself return path in self.__sourceRows else: # target is top level with no drop info for item in self.__sourceObjects: if not isinstance(item, model.Folder): # drop not permitted for no drop info because not folder return True # drop permitted for no drop info which is a folder return False # drop not permitted return True def __initTreeWidget(self): self.treeView.set_model(AkTreeModel(self.app.configManager.folders)) self.treeView.set_headers_visible(True) self.treeView.set_reorderable(False) self.treeView.set_rubber_banding(True) self.treeView.set_search_column(1) self.treeView.set_enable_search(True) targets = [] self.treeView.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, targets, Gdk.DragAction.DEFAULT|Gdk.DragAction.MOVE) self.treeView.enable_model_drag_dest(targets, Gdk.DragAction.DEFAULT) self.treeView.drag_source_add_text_targets() self.treeView.drag_dest_add_text_targets() self.treeView.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE) # Treeview columns column1 = Gtk.TreeViewColumn(_("Name")) iconRenderer = Gtk.CellRendererPixbuf() textRenderer = Gtk.CellRendererText() column1.pack_start(iconRenderer, False) column1.pack_end(textRenderer, True) column1.add_attribute(iconRenderer, "icon-name", 0) column1.add_attribute(textRenderer, "text", 1) column1.set_expand(True) column1.set_min_width(150) column1.set_sort_column_id(1) self.treeView.append_column(column1) column2 = Gtk.TreeViewColumn(_("Abbr.")) textRenderer = Gtk.CellRendererText() textRenderer.set_property("editable", False) column2.pack_start(textRenderer, True) column2.add_attribute(textRenderer, "text", 2) column2.set_expand(False) column2.set_min_width(50) self.treeView.append_column(column2) column3 = Gtk.TreeViewColumn(_("Hotkey")) textRenderer = Gtk.CellRendererText() textRenderer.set_property("editable", False) column3.pack_start(textRenderer, True) column3.add_attribute(textRenderer, "text", 3) column3.set_expand(False) column3.set_min_width(100) self.treeView.append_column(column3) def __popupMenu(self, event): menu = self.uiManager.get_widget("/Context") menu.popup(None, None, None, None, event.button, event.time) def __getattr__(self, attr): # Magic fudge to allow us to pretend to be the ui class we encapsulate return getattr(self.ui, attr) def __getTreeSelection(self): selection = self.treeView.get_selection() if selection is None: return [] model, items = selection.get_selected_rows() ret = [] if items: for item in items: value = model.get_value(model[item].iter, AkTreeModel.OBJECT_COLUMN) if value.parent not in ret: # Filter out any child objects that belong to a parent already in the list ret.append(value) return ret def __initStack(self): self.blankPage = BlankPage(self) self.folderPage = FolderPage(self) self.phrasePage = PhrasePage(self) self.scriptPage = ScriptPage(self) self.stack.append_page(self.blankPage.ui, None) self.stack.append_page(self.folderPage.ui, None) self.stack.append_page(self.phrasePage.ui, None) self.stack.append_page(self.scriptPage.ui, None) def promptToSave(self): selectedObject = self.__getTreeSelection() current = self.__getCurrentPage() result = False if self.dirty: if ConfigManager.SETTINGS[PROMPT_TO_SAVE]: dlg = Gtk.MessageDialog(self.ui, Gtk.DialogFlags.MODAL, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, _("There are unsaved changes. Would you like to save them?")) dlg.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) response = dlg.run() if response == Gtk.ResponseType.YES: self.on_save(None) elif response == Gtk.ResponseType.CANCEL: result = True dlg.destroy() else: result = self.on_save(None) return result def __getCurrentPage(self): #selectedObject = self.__getTreeSelection() if isinstance(self.selectedObject, model.Folder): return self.folderPage elif isinstance(self.selectedObject, model.Phrase): return self.phrasePage elif isinstance(self.selectedObject, model.Script): return self.scriptPage else: return None class AkTreeModel(Gtk.TreeStore): OBJECT_COLUMN = 4 def __init__(self, folders): Gtk.TreeStore.__init__(self, str, str, str, str, object) for folder in folders: iter = self.append(None, folder.get_tuple()) self.populate_store(iter, folder) self.folders = folders self.set_sort_func(1, self.compare) self.set_sort_column_id(1, Gtk.SortType.ASCENDING) def populate_store(self, parent, parentFolder): for folder in parentFolder.folders: iter = self.append(parent, folder.get_tuple()) self.populate_store(iter, folder) for item in parentFolder.items: self.append(parent, item.get_tuple()) def append_item(self, item, parentIter): if parentIter is None: self.folders.append(item) item.parent = None return self.append(None, item.get_tuple()) else: parentFolder = self.get_value(parentIter, self.OBJECT_COLUMN) if isinstance(item, model.Folder): parentFolder.add_folder(item) else: parentFolder.add_item(item) return self.append(parentIter, item.get_tuple()) def remove_item(self, iter): item = self.get_value(iter, self.OBJECT_COLUMN) item.remove_data() if item.parent is None: self.folders.remove(item) else: if isinstance(item, model.Folder): item.parent.remove_folder(item) else: item.parent.remove_item(item) self.remove(iter) def update_item(self, targetIter, items): for item in items: itemTuple = item.get_tuple() updateList = [] for n in range(len(itemTuple)): updateList.append(n) updateList.append(itemTuple[n]) self.set(targetIter, *updateList) def compare(self, theModel, iter1, iter2, data=None): item1 = theModel.get_value(iter1, AkTreeModel.OBJECT_COLUMN) item2 = theModel.get_value(iter2, AkTreeModel.OBJECT_COLUMN) if isinstance(item1, model.Folder) and (isinstance(item2, model.Phrase) or isinstance(item2, model.Script)): return -1 elif isinstance(item2, model.Folder) and (isinstance(item1, model.Phrase) or isinstance(item1, model.Script)): return 1 else: return cmp(str(item1), str(item2)) autokey-0.90.4/src/lib/gtkui/notifier.py0000664000175000017500000003122311754434331017150 0ustar chrischris# -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see .. #import pynotify, gtk, gettext from gi.repository import Gtk, Gdk, Notify import gettext import popupmenu from autokey.configmanager import * from autokey import common HAVE_APPINDICATOR = False try: from gi.repository import AppIndicator3 HAVE_APPINDICATOR = True except ImportError: pass gettext.install("autokey") TOOLTIP_RUNNING = _("AutoKey - running") TOOLTIP_PAUSED = _("AutoKey - paused") def get_notifier(app): if HAVE_APPINDICATOR: return IndicatorNotifier(app) else: return Notifier(app) class Notifier: """ Encapsulates all functionality related to the notification icon, notifications, and tray menu. """ def __init__(self, autokeyApp): Notify.init("AutoKey") self.app = autokeyApp self.configManager = autokeyApp.service.configManager self.icon = Gtk.StatusIcon.new_from_icon_name(ConfigManager.SETTINGS[NOTIFICATION_ICON]) self.update_tool_tip() self.icon.connect("popup_menu", self.on_popup_menu) self.icon.connect("activate", self.on_show_configure) self.errorItem = None self.update_visible_status() def update_visible_status(self): if ConfigManager.SETTINGS[SHOW_TRAY_ICON]: self.icon.set_visible(True) else: self.icon.set_visible(False) def update_tool_tip(self): if ConfigManager.SETTINGS[SHOW_TRAY_ICON]: if ConfigManager.SETTINGS[SERVICE_RUNNING]: self.icon.set_tooltip_text(TOOLTIP_RUNNING) else: self.icon.set_tooltip_text(TOOLTIP_PAUSED) def hide_icon(self): self.icon.set_visible(False) def rebuild_menu(self): pass # Signal Handlers ---- def on_popup_menu(self, status_icon, button, activate_time, data=None): # Main Menu items enableMenuItem = Gtk.CheckMenuItem(_("Enable Expansions")) enableMenuItem.set_active(self.app.service.is_running()) enableMenuItem.set_sensitive(not self.app.serviceDisabled) configureMenuItem = Gtk.ImageMenuItem(_("Show Main Window")) configureMenuItem.set_image(Gtk.Image.new_from_stock(Gtk.STOCK_PREFERENCES, Gtk.IconSize.MENU)) removeMenuItem = Gtk.ImageMenuItem(_("Remove icon")) removeMenuItem.set_image(Gtk.Image.new_from_stock(Gtk.STOCK_CLOSE, Gtk.IconSize.MENU)) quitMenuItem = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_QUIT, None) # Menu signals enableMenuItem.connect("toggled", self.on_enable_toggled) configureMenuItem.connect("activate", self.on_show_configure) removeMenuItem.connect("activate", self.on_remove_icon) quitMenuItem.connect("activate", self.on_destroy_and_exit) # Get phrase folders to add to main menu folders = [] items = [] for folder in self.configManager.allFolders: if folder.showInTrayMenu: folders.append(folder) for item in self.configManager.allItems: if item.showInTrayMenu: items.append(item) # Construct main menu menu = popupmenu.PopupMenu(self.app.service, folders, items, False) if len(items) > 0: menu.append(Gtk.SeparatorMenuItem()) menu.append(enableMenuItem) if self.errorItem is not None: menu.append(self.errorItem) menu.append(configureMenuItem) menu.append(removeMenuItem) menu.append(quitMenuItem) menu.show_all() menu.popup(None, None, None, None, button, activate_time) def on_enable_toggled(self, widget, data=None): if widget.active: self.app.unpause_service() else: self.app.pause_service() def on_show_configure(self, widget, data=None): self.app.show_configure() def on_remove_icon(self, widget, data=None): self.icon.set_visible(False) ConfigManager.SETTINGS[SHOW_TRAY_ICON] = False def on_destroy_and_exit(self, widget, data=None): self.app.shutdown() def notify_error(self, message): self.show_notify(message, Gtk.STOCK_DIALOG_ERROR) self.errorItem = Gtk.MenuItem(_("View script error")) self.errorItem.connect("activate", self.on_show_error) self.icon.set_from_icon_name(common.ICON_FILE_NOTIFICATION_ERROR) def on_show_error(self, widget, data=None): self.app.show_script_error() self.errorItem = None self.icon.set_from_icon_name(ConfigManager.SETTINGS[NOTIFICATION_ICON]) def show_notify(self, message, iconName): Gdk.threads_enter() n = Notify.Notification.new("AutoKey", message, iconName) n.set_urgency(Notify.Urgency.LOW) if ConfigManager.SETTINGS[SHOW_TRAY_ICON]: n.attach_to_status_icon(self.icon) n.show() Gdk.threads_leave() class IndicatorNotifier: def __init__(self, autokeyApp): Notify.init("AutoKey") self.app = autokeyApp self.configManager = autokeyApp.service.configManager self.indicator = AppIndicator3.Indicator.new("AutoKey", ConfigManager.SETTINGS[NOTIFICATION_ICON], AppIndicator3.IndicatorCategory.APPLICATION_STATUS) self.indicator.set_attention_icon(common.ICON_FILE_NOTIFICATION_ERROR) self.update_visible_status() self.rebuild_menu() def update_visible_status(self): if ConfigManager.SETTINGS[SHOW_TRAY_ICON]: self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE) else: self.indicator.set_status(AppIndicator3.IndicatorStatus.PASSIVE) def hide_icon(self): self.indicator.set_status(AppIndicator3.IndicatorStatus.PASSIVE) def rebuild_menu(self): # Main Menu items self.errorItem = Gtk.MenuItem(_("View script error")) enableMenuItem = Gtk.CheckMenuItem(_("Enable Expansions")) enableMenuItem.set_active(self.app.service.is_running()) enableMenuItem.set_sensitive(not self.app.serviceDisabled) configureMenuItem = Gtk.ImageMenuItem(_("Show Main Window")) configureMenuItem.set_image(Gtk.Image.new_from_stock(Gtk.STOCK_PREFERENCES, Gtk.IconSize.MENU)) removeMenuItem = Gtk.ImageMenuItem(_("Remove icon")) removeMenuItem.set_image(Gtk.Image.new_from_stock(Gtk.STOCK_CLOSE, Gtk.IconSize.MENU)) quitMenuItem = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_QUIT, None) # Menu signals enableMenuItem.connect("toggled", self.on_enable_toggled) configureMenuItem.connect("activate", self.on_show_configure) removeMenuItem.connect("activate", self.on_remove_icon) quitMenuItem.connect("activate", self.on_destroy_and_exit) self.errorItem.connect("activate", self.on_show_error) # Get phrase folders to add to main menu folders = [] items = [] for folder in self.configManager.allFolders: if folder.showInTrayMenu: folders.append(folder) for item in self.configManager.allItems: if item.showInTrayMenu: items.append(item) # Construct main menu self.menu = popupmenu.PopupMenu(self.app.service, folders, items, False) if len(items) > 0: self.menu.append(Gtk.SeparatorMenuItem()) self.menu.append(self.errorItem) self.menu.append(enableMenuItem) self.menu.append(configureMenuItem) self.menu.append(removeMenuItem) self.menu.append(quitMenuItem) self.menu.show_all() self.errorItem.hide() self.indicator.set_menu(self.menu) def notify_error(self, message): self.show_notify(message, Gtk.STOCK_DIALOG_ERROR) self.errorItem.show() self.indicator.set_status(AppIndicator3.IndicatorStatus.ATTENTION) def show_notify(self, message, iconName): Gdk.threads_enter() n = Notify.Notification.new("AutoKey", message, iconName) n.set_urgency(Notify.Urgency.LOW) n.show() Gdk.threads_leave() def update_tool_tip(self): pass def on_show_error(self, widget, data=None): self.app.show_script_error() self.errorItem.hide() self.update_visible_status() def on_enable_toggled(self, widget, data=None): if widget.active: self.app.unpause_service() else: self.app.pause_service() def on_show_configure(self, widget, data=None): self.app.show_configure() def on_remove_icon(self, widget, data=None): self.indicator.set_status(AppIndicator3.IndicatorStatus.PASSIVE) ConfigManager.SETTINGS[SHOW_TRAY_ICON] = False def on_destroy_and_exit(self, widget, data=None): self.app.shutdown() class UnityLauncher(IndicatorNotifier): SHOW_ITEM_STRING = _("Add to quicklist/notification menu") #def __init__(self, autokeyApp): # IndicatorNotifier.__init__(self, autokeyApp) def __getQuickItem(self, label): item = Dbusmenu.Menuitem.new() item.property_set(Dbusmenu.MENUITEM_PROP_LABEL, label) item.property_set_bool(Dbusmenu.MENUITEM_PROP_VISIBLE, True) return item def rebuild_menu(self): IndicatorNotifier.rebuild_menu(self) print threading.currentThread().name #try: from gi.repository import Unity, Dbusmenu HAVE_UNITY = True print "have unity" #except ImportError: # return print "rebuild unity menu" self.launcher = Unity.LauncherEntry.get_for_desktop_id ("autokey-gtk.desktop") # Main Menu items enableMenuItem = self.__getQuickItem(_("Enable Expansions")) enableMenuItem.property_set(Dbusmenu.MENUITEM_PROP_TOGGLE_TYPE, Dbusmenu.MENUITEM_TOGGLE_CHECK) #if self.app.service.is_running(): # enableMenuItem.property_set_int(Dbusmenu.MENUITEM_PROP_TOGGLE_STATE, Dbusmenu.MENUITEM_TOGGLE_STATE_CHECKED) #else: # enableMenuItem.property_set_int(Dbusmenu.MENUITEM_PROP_TOGGLE_STATE, Dbusmenu.MENUITEM_TOGGLE_STATE_UNCHECKED) enableMenuItem.property_set_int(Dbusmenu.MENUITEM_PROP_TOGGLE_STATE, int(self.app.service.is_running())) enableMenuItem.property_set_bool(Dbusmenu.MENUITEM_PROP_ENABLED, not self.app.serviceDisabled) configureMenuItem = self.__getQuickItem(_("Show Main Window")) # Menu signals enableMenuItem.connect("item-activated", self.on_ql_enable_toggled, None) configureMenuItem.connect("item-activated", self.on_show_configure, None) # Get phrase folders to add to main menu # folders = [] # items = [] # for folder in self.configManager.allFolders: # if folder.showInTrayMenu: # folders.append(folder) # # for item in self.configManager.allItems: # if item.showInTrayMenu: # items.append(item) # Construct main menu quicklist = Dbusmenu.Menuitem.new() #if len(items) > 0: # self.menu.append(Gtk.SeparatorMenuItem()) quicklist.child_append(enableMenuItem) quicklist.child_append(configureMenuItem) self.launcher.set_property ("quicklist", quicklist) def on_ql_enable_toggled(self, menuitem, data=None): if menuitem.property_get_int(Menuitem.MENUITEM_PROP_TOGGLE_STATE) == Menuitem.MENUITEM_TOGGLE_STATE_CHECKED: self.app.unpause_service() else: self.app.pause_service() autokey-0.90.4/src/lib/gtkui/__init__.py0000664000175000017500000000000011354567042017057 0ustar chrischrisautokey-0.90.4/src/lib/gtkui/settingsdialog.py0000664000175000017500000002163211746515472020363 0ustar chrischris# -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os, sys from gi.repository import Gtk from autokey.configmanager import * from autokey import iomediator, model, common from dialogs import GlobalHotkeyDialog import configwindow DESKTOP_FILE = "/usr/share/applications/autokey-gtk.desktop" AUTOSTART_DIR = os.path.expanduser("~/.config/autostart") AUTOSTART_FILE = os.path.join(AUTOSTART_DIR, "autokey-gtk.desktop") ICON_NAME_MAP = { _("Light") : common.ICON_FILE_NOTIFICATION, _("Dark") : common.ICON_FILE_NOTIFICATION_DARK } ICON_NAME_LIST = [] class SettingsDialog: KEY_MAP = GlobalHotkeyDialog.KEY_MAP REVERSE_KEY_MAP = GlobalHotkeyDialog.REVERSE_KEY_MAP def __init__(self, parent, configManager): builder = configwindow.get_ui("settingsdialog.xml") self.ui = builder.get_object("settingsdialog") builder.connect_signals(self) self.ui.set_transient_for(parent) self.configManager = configManager # General Settings self.autoStartCheckbox = builder.get_object("autoStartCheckbox") self.promptToSaveCheckbox = builder.get_object("promptToSaveCheckbox") self.showTrayCheckbox = builder.get_object("showTrayCheckbox") self.allowKbNavCheckbox = builder.get_object("allowKbNavCheckbox") self.allowKbNavCheckbox.hide() self.sortByUsageCheckbox = builder.get_object("sortByUsageCheckbox") self.enableUndoCheckbox = builder.get_object("enableUndoCheckbox") self.iconStyleCombo = Gtk.ComboBoxText.new() hbox = builder.get_object("hbox4") hbox.pack_start(self.iconStyleCombo, False, True, 0) hbox.show_all() for key, value in ICON_NAME_MAP.items(): self.iconStyleCombo.append_text(key) ICON_NAME_LIST.append(value) self.iconStyleCombo.set_sensitive(ConfigManager.SETTINGS[SHOW_TRAY_ICON]) self.iconStyleCombo.set_active(ICON_NAME_LIST.index(ConfigManager.SETTINGS[NOTIFICATION_ICON])) self.autoStartCheckbox.set_active(os.path.exists(AUTOSTART_FILE)) self.promptToSaveCheckbox.set_active(ConfigManager.SETTINGS[PROMPT_TO_SAVE]) self.showTrayCheckbox.set_active(ConfigManager.SETTINGS[SHOW_TRAY_ICON]) #self.allowKbNavCheckbox.set_active(ConfigManager.SETTINGS[MENU_TAKES_FOCUS]) self.sortByUsageCheckbox.set_active(ConfigManager.SETTINGS[SORT_BY_USAGE_COUNT]) self.enableUndoCheckbox.set_active(ConfigManager.SETTINGS[UNDO_USING_BACKSPACE]) # Hotkeys self.showConfigDlg = GlobalHotkeyDialog(parent, configManager, self.on_config_response) self.toggleMonitorDlg = GlobalHotkeyDialog(parent, configManager, self.on_monitor_response) self.configKeyLabel = builder.get_object("configKeyLabel") self.clearConfigButton = builder.get_object("clearConfigButton") self.monitorKeyLabel = builder.get_object("monitorKeyLabel") self.clearMonitorButton = builder.get_object("clearMonitorButton") self.useConfigHotkey = self.__loadHotkey(configManager.configHotkey, self.configKeyLabel, self.showConfigDlg, self.clearConfigButton) self.useServiceHotkey = self.__loadHotkey(configManager.toggleServiceHotkey, self.monitorKeyLabel, self.toggleMonitorDlg, self.clearMonitorButton) # Script Engine Settings self.userModuleChooserButton = builder.get_object("userModuleChooserButton") if configManager.userCodeDir is not None: self.userModuleChooserButton.set_current_folder(configManager.userCodeDir) if configManager.userCodeDir in sys.path: sys.path.remove(configManager.userCodeDir) def on_save(self, widget, data=None): if self.autoStartCheckbox.get_active(): if not os.path.exists(AUTOSTART_FILE): try: inFile = open(DESKTOP_FILE, 'r') outFile = open(AUTOSTART_FILE, 'w') contents = inFile.read() contents = contents.replace(" -c\n", "\n") outFile.write(contents) inFile.close() outFile.close() except: pass else: if os.path.exists(AUTOSTART_FILE): os.remove(AUTOSTART_FILE) ConfigManager.SETTINGS[PROMPT_TO_SAVE] = self.promptToSaveCheckbox.get_active() ConfigManager.SETTINGS[SHOW_TRAY_ICON] = self.showTrayCheckbox.get_active() #ConfigManager.SETTINGS[MENU_TAKES_FOCUS] = self.allowKbNavCheckbox.get_active() ConfigManager.SETTINGS[SORT_BY_USAGE_COUNT] = self.sortByUsageCheckbox.get_active() ConfigManager.SETTINGS[UNDO_USING_BACKSPACE] = self.enableUndoCheckbox.get_active() ConfigManager.SETTINGS[NOTIFICATION_ICON] = ICON_NAME_MAP[self.iconStyleCombo.get_active_text()] self.configManager.userCodeDir = self.userModuleChooserButton.get_current_folder() sys.path.append(self.configManager.userCodeDir) configHotkey = self.configManager.configHotkey toggleHotkey = self.configManager.toggleServiceHotkey app = self.configManager.app if configHotkey.enabled: app.hotkey_removed(configHotkey) configHotkey.enabled = self.useConfigHotkey if self.useConfigHotkey: self.showConfigDlg.save(configHotkey) app.hotkey_created(configHotkey) if toggleHotkey.enabled: app.hotkey_removed(toggleHotkey) toggleHotkey.enabled = self.useServiceHotkey if self.useServiceHotkey: self.toggleMonitorDlg.save(toggleHotkey) app.hotkey_created(toggleHotkey) app.update_notifier_visibility() self.configManager.config_altered(True) self.hide() self.destroy() def on_cancel(self, widget, data=None): self.hide() self.destroy() def __getattr__(self, attr): # Magic fudge to allow us to pretend to be the ui class we encapsulate return getattr(self.ui, attr) def __loadHotkey(self, item, label, dialog, clearButton): dialog.load(item) if item.enabled: key = item.hotKey.encode("utf-8") label.set_text(item.get_hotkey_string()) clearButton.set_sensitive(True) return True else: label.set_text(_("(None configured)")) clearButton.set_sensitive(False) return False # ---- Signal handlers def on_showTrayCheckbox_toggled(self, widget, data=None): self.iconStyleCombo.set_sensitive(widget.get_active()) def on_setConfigButton_pressed(self, widget, data=None): self.showConfigDlg.run() def on_config_response(self, res): if res == Gtk.ResponseType.OK: self.useConfigHotkey = True key = self.showConfigDlg.key modifiers = self.showConfigDlg.build_modifiers() self.configKeyLabel.set_text(self.build_hotkey_string(key, modifiers)) self.clearConfigButton.set_sensitive(True) def on_clearConfigButton_pressed(self, widget, data=None): self.useConfigHotkey = False self.clearConfigButton.set_sensitive(False) self.configKeyLabel.set_text(_("(None configured)")) self.showConfigDlg.reset() def on_setMonitorButton_pressed(self, widget, data=None): self.toggleMonitorDlg.run() def on_monitor_response(self, res): if res == Gtk.ResponseType.OK: self.useServiceHotkey = True key = self.toggleMonitorDlg.key modifiers = self.toggleMonitorDlg.build_modifiers() self.monitorKeyLabel.set_text(self.build_hotkey_string(key, modifiers)) self.clearMonitorButton.set_sensitive(True) def on_clearMonitorButton_pressed(self, widget, data=None): self.useServiceHotkey = False self.clearMonitorButton.set_sensitive(False) self.monitorKeyLabel.set_text(_("(None configured)")) self.toggleMonitorDlg.reset() autokey-0.90.4/src/lib/gtkui/dialogs.py0000664000175000017500000005454711754436675017006 0ustar chrischris# -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import logging, sys, os, re from gi.repository import Gtk, Gdk #import gettext import locale GETTEXT_DOMAIN = 'autokey' locale.setlocale(locale.LC_ALL, '') #for module in Gtk.glade, gettext: # module.bindtextdomain(GETTEXT_DOMAIN) # module.textdomain(GETTEXT_DOMAIN) __all__ = ["validate", "EMPTY_FIELD_REGEX", "AbbrSettingsDialog", "HotkeySettingsDialog", "WindowFilterSettingsDialog", "RecordDialog"] from autokey import model, iomediator import configwindow WORD_CHAR_OPTIONS = { "All non-word" : model.DEFAULT_WORDCHAR_REGEX, "Space and Enter" : r"[^ \n]", "Tab" : r"[^\t]" } WORD_CHAR_OPTIONS_ORDERED = ["All non-word", "Space and Enter", "Tab"] EMPTY_FIELD_REGEX = re.compile(r"^ *$", re.UNICODE) def validate(expression, message, widget, parent): if not expression: dlg = Gtk.MessageDialog(parent, Gtk.DialogFlags.MODAL|Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.WARNING, Gtk.ButtonsType.OK, message) dlg.run() dlg.destroy() if widget is not None: widget.grab_focus() return expression class DialogBase: def __init__(self): self.connect("close", self.on_close) self.connect("delete_event", self.on_close) def on_close(self, widget, data=None): self.hide() return True def on_cancel(self, widget, data=None): self.load(self.targetItem) self.ui.response(Gtk.ResponseType.CANCEL) self.hide() def on_ok(self, widget, data=None): if self.valid(): self.response(Gtk.ResponseType.OK) self.hide() def __getattr__(self, attr): # Magic fudge to allow us to pretend to be the ui class we encapsulate return getattr(self.ui, attr) def on_response(self, widget, responseId): self.closure(responseId) if responseId < 0: self.hide() self.emit_stop_by_name('response') class AbbrSettingsDialog(DialogBase): def __init__(self, parent, configManager, closure): builder = configwindow.get_ui("abbrsettings.xml") self.ui = builder.get_object("abbrsettings") builder.connect_signals(self) self.ui.set_transient_for(parent) self.configManager = configManager self.closure = closure self.abbrList = builder.get_object("abbrList") self.addButton = builder.get_object("addButton") self.removeButton = builder.get_object("removeButton") self.wordCharCombo = builder.get_object("wordCharCombo") self.removeTypedCheckbox = builder.get_object("removeTypedCheckbox") self.omitTriggerCheckbox = builder.get_object("omitTriggerCheckbox") self.matchCaseCheckbox = builder.get_object("matchCaseCheckbox") self.ignoreCaseCheckbox = builder.get_object("ignoreCaseCheckbox") self.triggerInsideCheckbox = builder.get_object("triggerInsideCheckbox") self.immediateCheckbox = builder.get_object("immediateCheckbox") DialogBase.__init__(self) # set up list view store = Gtk.ListStore(str) self.abbrList.set_model(store) column1 = Gtk.TreeViewColumn(_("Abbreviations")) textRenderer = Gtk.CellRendererText() textRenderer.set_property("editable", True) textRenderer.connect("edited", self.on_cell_modified) textRenderer.connect("editing-canceled", self.on_cell_editing_cancelled) column1.pack_end(textRenderer, True) column1.add_attribute(textRenderer, "text", 0) column1.set_sizing(Gtk.TreeViewColumnSizing.FIXED) self.abbrList.append_column(column1) for item in WORD_CHAR_OPTIONS_ORDERED: self.wordCharCombo.append_text(item) def load(self, item): self.targetItem = item self.abbrList.get_model().clear() if model.TriggerMode.ABBREVIATION in item.modes: for abbr in item.abbreviations: self.abbrList.get_model().append((abbr.encode("utf-8"),)) self.removeButton.set_sensitive(True) firstIter = self.abbrList.get_model().get_iter_first() self.abbrList.get_selection().select_iter(firstIter) else: self.removeButton.set_sensitive(False) self.removeTypedCheckbox.set_active(item.backspace) self.__resetWordCharCombo() wordCharRegex = item.get_word_chars() if wordCharRegex in WORD_CHAR_OPTIONS.values(): # Default wordchar regex used for desc, regex in WORD_CHAR_OPTIONS.iteritems(): if item.get_word_chars() == regex: self.wordCharCombo.set_active(WORD_CHAR_OPTIONS_ORDERED.index(desc)) break else: # Custom wordchar regex used self.wordCharCombo.append_text(model.extract_wordchars(wordCharRegex).encode("utf-8")) self.wordCharCombo.set_active(len(WORD_CHAR_OPTIONS)) if isinstance(item, model.Folder): self.omitTriggerCheckbox.hide() else: self.omitTriggerCheckbox.show() self.omitTriggerCheckbox.set_active(item.omitTrigger) if isinstance(item, model.Phrase): self.matchCaseCheckbox.show() self.matchCaseCheckbox.set_active(item.matchCase) else: self.matchCaseCheckbox.hide() self.ignoreCaseCheckbox.set_active(item.ignoreCase) self.triggerInsideCheckbox.set_active(item.triggerInside) self.immediateCheckbox.set_active(item.immediate) def save(self, item): item.modes.append(model.TriggerMode.ABBREVIATION) item.clear_abbreviations() item.abbreviations = self.get_abbrs() item.backspace = self.removeTypedCheckbox.get_active() option = self.wordCharCombo.get_active_text() if option in WORD_CHAR_OPTIONS: item.set_word_chars(WORD_CHAR_OPTIONS[option]) else: item.set_word_chars(model.make_wordchar_re(option)) if not isinstance(item, model.Folder): item.omitTrigger = self.omitTriggerCheckbox.get_active() if isinstance(item, model.Phrase): item.matchCase = self.matchCaseCheckbox.get_active() item.ignoreCase = self.ignoreCaseCheckbox.get_active() item.triggerInside = self.triggerInsideCheckbox.get_active() item.immediate = self.immediateCheckbox.get_active() def reset(self): self.abbrList.get_model().clear() self.__resetWordCharCombo() self.removeButton.set_sensitive(False) self.wordCharCombo.set_active(0) self.omitTriggerCheckbox.set_active(False) self.removeTypedCheckbox.set_active(True) self.matchCaseCheckbox.set_active(False) self.ignoreCaseCheckbox.set_active(False) self.triggerInsideCheckbox.set_active(False) self.immediateCheckbox.set_active(False) def __resetWordCharCombo(self): self.wordCharCombo.remove_all() for item in WORD_CHAR_OPTIONS_ORDERED: self.wordCharCombo.append_text(item) self.wordCharCombo.set_active(0) def get_abbrs(self): ret = [] model = self.abbrList.get_model() i = iter(model) try: while True: text = model.get_value(i.next().iter, 0) ret.append(text.decode("utf-8")) except StopIteration: pass return list(set(ret)) def get_abbrs_readable(self): abbrs = self.get_abbrs() if len(abbrs) == 1: return abbrs[0].encode("utf-8") else: return "[%s]" % ','.join([a.encode("utf-8") for a in abbrs]) def valid(self): if not validate(len(self.get_abbrs()) > 0, _("You must specify at least one abbreviation"), self.addButton, self.ui): return False return True def reset_focus(self): self.addButton.grab_focus() # Signal handlers def on_cell_editing_cancelled(self, renderer, data=None): model, curIter = self.abbrList.get_selection().get_selected() oldText = model.get_value(curIter, 0) or "" self.on_cell_modified(renderer, None, oldText) def on_cell_modified(self, renderer, path, newText, data=None): model, curIter = self.abbrList.get_selection().get_selected() oldText = model.get_value(curIter, 0) or "" if EMPTY_FIELD_REGEX.match(newText) and EMPTY_FIELD_REGEX.match(oldText): self.on_removeButton_clicked(renderer) else: model.set(curIter, 0, newText) def on_addButton_clicked(self, widget, data=None): model = self.abbrList.get_model() newIter = model.append() self.abbrList.set_cursor(model.get_path(newIter), self.abbrList.get_column(0), True) self.removeButton.set_sensitive(True) def on_removeButton_clicked(self, widget, data=None): model, curIter = self.abbrList.get_selection().get_selected() model.remove(curIter) if model.get_iter_first() is None: self.removeButton.set_sensitive(False) else: self.abbrList.get_selection().select_iter(model.get_iter_first()) def on_abbrList_cursorchanged(self, widget, data=None): pass def on_ignoreCaseCheckbox_stateChanged(self, widget, data=None): if not self.ignoreCaseCheckbox.get_active(): self.matchCaseCheckbox.set_active(False) def on_matchCaseCheckbox_stateChanged(self, widget, data=None): if self.matchCaseCheckbox.get_active(): self.ignoreCaseCheckbox.set_active(True) def on_immediateCheckbox_stateChanged(self, widget, data=None): if self.immediateCheckbox.get_active(): self.omitTriggerCheckbox.set_active(False) self.omitTriggerCheckbox.set_sensitive(False) self.wordCharCombo.set_sensitive(False) else: self.omitTriggerCheckbox.set_sensitive(True) self.wordCharCombo.set_sensitive(True) class HotkeySettingsDialog(DialogBase): KEY_MAP = { ' ' : "", } REVERSE_KEY_MAP = {} for key, value in KEY_MAP.iteritems(): REVERSE_KEY_MAP[value] = key def __init__(self, parent, configManager, closure): builder = configwindow.get_ui("hotkeysettings.xml") self.ui = builder.get_object("hotkeysettings") builder.connect_signals(self) self.ui.set_transient_for(parent) self.configManager = configManager self.closure = closure self.key = None self.controlButton = builder.get_object("controlButton") self.altButton = builder.get_object("altButton") self.shiftButton = builder.get_object("shiftButton") self.superButton = builder.get_object("superButton") self.hyperButton = builder.get_object("hyperButton") self.metaButton = builder.get_object("metaButton") self.setButton = builder.get_object("setButton") self.keyLabel = builder.get_object("keyLabel") DialogBase.__init__(self) def load(self, item): self.targetItem = item self.setButton.set_sensitive(True) if model.TriggerMode.HOTKEY in item.modes: self.controlButton.set_active(iomediator.Key.CONTROL in item.modifiers) self.altButton.set_active(iomediator.Key.ALT in item.modifiers) self.shiftButton.set_active(iomediator.Key.SHIFT in item.modifiers) self.superButton.set_active(iomediator.Key.SUPER in item.modifiers) self.hyperButton.set_active(iomediator.Key.HYPER in item.modifiers) self.metaButton.set_active(iomediator.Key.META in item.modifiers) key = item.hotKey if key in self.KEY_MAP: keyText = self.KEY_MAP[key] else: keyText = key self._setKeyLabel(keyText) self.key = keyText else: self.reset() def save(self, item): item.modes.append(model.TriggerMode.HOTKEY) # Build modifier list modifiers = self.build_modifiers() keyText = self.key if keyText in self.REVERSE_KEY_MAP: key = self.REVERSE_KEY_MAP[keyText] else: key = keyText assert key != None, "Attempt to set hotkey with no key" item.set_hotkey(modifiers, key) def reset(self): self.controlButton.set_active(False) self.altButton.set_active(False) self.shiftButton.set_active(False) self.superButton.set_active(False) self.hyperButton.set_active(False) self.metaButton.set_active(False) self._setKeyLabel(_("(None)")) self.key = None self.setButton.set_sensitive(True) def set_key(self, key, modifiers=[]): Gdk.threads_enter() if self.KEY_MAP.has_key(key): key = self.KEY_MAP[key] self._setKeyLabel(key) self.key = key self.controlButton.set_active(iomediator.Key.CONTROL in modifiers) self.altButton.set_active(iomediator.Key.ALT in modifiers) self.shiftButton.set_active(iomediator.Key.SHIFT in modifiers) self.superButton.set_active(iomediator.Key.SUPER in modifiers) self.hyperButton.set_active(iomediator.Key.HYPER in modifiers) self.metaButton.set_active(iomediator.Key.META in modifiers) self.setButton.set_sensitive(True) Gdk.threads_leave() def cancel_grab(self): Gdk.threads_enter() self.setButton.set_sensitive(True) self._setKeyLabel(self.key) Gdk.threads_leave() def build_modifiers(self): modifiers = [] if self.controlButton.get_active(): modifiers.append(iomediator.Key.CONTROL) if self.altButton.get_active(): modifiers.append(iomediator.Key.ALT) if self.shiftButton.get_active(): modifiers.append(iomediator.Key.SHIFT) if self.superButton.get_active(): modifiers.append(iomediator.Key.SUPER) if self.hyperButton.get_active(): modifiers.append(iomediator.Key.HYPER) if self.metaButton.get_active(): modifiers.append(iomediator.Key.META) modifiers.sort() return modifiers def _setKeyLabel(self, key): self.keyLabel.set_text(_("Key: ") + key) def valid(self): if not validate(self.key is not None, _("You must specify a key for the hotkey."), None, self.ui): return False return True def on_setButton_pressed(self, widget, data=None): self.setButton.set_sensitive(False) self.keyLabel.set_text(_("Press a key...")) self.grabber = iomediator.KeyGrabber(self) self.grabber.start() class GlobalHotkeyDialog(HotkeySettingsDialog): def load(self, item): self.targetItem = item if item.enabled: self.controlButton.set_active(iomediator.Key.CONTROL in item.modifiers) self.altButton.set_active(iomediator.Key.ALT in item.modifiers) self.shiftButton.set_active(iomediator.Key.SHIFT in item.modifiers) self.superButton.set_active(iomediator.Key.SUPER in item.modifiers) self.hyperButton.set_active(iomediator.Key.HYPER in item.modifiers) self.metaButton.set_active(iomediator.Key.META in item.modifiers) key = item.hotKey if key in self.KEY_MAP: keyText = self.KEY_MAP[key] else: keyText = key self._setKeyLabel(keyText) self.key = keyText else: self.reset() def save(self, item): # Build modifier list modifiers = self.build_modifiers() keyText = self.key if keyText in self.REVERSE_KEY_MAP: key = self.REVERSE_KEY_MAP[keyText] else: key = keyText assert key != None, "Attempt to set hotkey with no key" item.set_hotkey(modifiers, key) def valid(self): configManager = self.configManager modifiers = self.build_modifiers() regex = self.targetItem.get_applicable_regex() pattern = None if regex is not None: pattern = regex.pattern unique, conflicting = configManager.check_hotkey_unique(modifiers, self.key, pattern, self.targetItem) if not validate(unique, _("The hotkey is already in use for %s.") % conflicting, None, self.ui): return False if not validate(self.key is not None, _("You must specify a key for the hotkey."), None, self.ui): return False return True class WindowFilterSettingsDialog(DialogBase): def __init__(self, parent, closure): builder = configwindow.get_ui("windowfiltersettings.xml") self.ui = builder.get_object("windowfiltersettings") builder.connect_signals(self) self.ui.set_transient_for(parent) self.closure = closure self.triggerRegexEntry = builder.get_object("triggerRegexEntry") self.recursiveButton = builder.get_object("recursiveButton") self.detectButton = builder.get_object("detectButton") DialogBase.__init__(self) def load(self, item): self.targetItem = item if not isinstance(item, model.Folder): self.recursiveButton.hide() else: self.recursiveButton.show() if not item.has_filter(): self.reset() else: self.triggerRegexEntry.set_text(item.get_filter_regex()) self.recursiveButton.set_active(item.isRecursive) def save(self, item): item.set_window_titles(self.get_filter_text()) item.set_filter_recursive(self.get_is_recursive()) def reset(self): self.triggerRegexEntry.set_text("") self.recursiveButton.set_active(False) def get_filter_text(self): return self.triggerRegexEntry.get_text().decode("utf-8") def get_is_recursive(self): return self.recursiveButton.get_active() def valid(self): return True def reset_focus(self): self.triggerRegexEntry.grab_focus() def on_response(self, widget, responseId): self.closure(responseId) def receive_window_info(self, info): Gdk.threads_enter() dlg = DetectDialog(self.ui) dlg.populate(info) response = dlg.run() if response == Gtk.ResponseType.OK: self.triggerRegexEntry.set_text(dlg.get_choice().encode("utf-8")) self.detectButton.set_sensitive(True) Gdk.threads_leave() def on_detectButton_pressed(self, widget, data=None): #self.__dlg = widget.set_sensitive(False) self.grabber = iomediator.WindowGrabber(self) self.grabber.start() class DetectDialog(DialogBase): def __init__(self, parent): builder = configwindow.get_ui("detectdialog.xml") self.ui = builder.get_object("detectdialog") builder.connect_signals(self) self.ui.set_transient_for(parent) self.classLabel = builder.get_object("classLabel") self.titleLabel = builder.get_object("titleLabel") self.classRadioButton = builder.get_object("classRadioButton") self.titleRadioButton = builder.get_object("titleRadioButton") DialogBase.__init__(self) def populate(self, windowInfo): self.titleLabel.set_text(_("Window title: %s") % windowInfo[0].encode("utf-8")) self.classLabel.set_text(_("Window class: %s") % windowInfo[1].encode("utf-8")) self.windowInfo = windowInfo def get_choice(self): if self.classRadioButton.get_active(): return self.windowInfo[1] else: return self.windowInfo[0] def on_cancel(self, widget, data=None): self.ui.response(Gtk.ResponseType.CANCEL) self.hide() def on_ok(self, widget, data=None): self.response(Gtk.ResponseType.OK) self.hide() class RecordDialog(DialogBase): def __init__(self, parent, closure): self.closure = closure builder = configwindow.get_ui("recorddialog.xml") self.ui = builder.get_object("recorddialog") builder.connect_signals(self) self.ui.set_transient_for(parent) self.keyboardButton = builder.get_object("keyboardButton") self.mouseButton = builder.get_object("mouseButton") self.spinButton = builder.get_object("spinButton") DialogBase.__init__(self) def get_record_keyboard(self): return self.keyboardButton.get_active() def get_record_mouse(self): return self.mouseButton.get_active() def get_delay(self): return self.spinButton.get_value_as_int() def on_response(self, widget, responseId): self.closure(responseId, self.get_record_keyboard(), self.get_record_mouse(), self.get_delay()) def on_cancel(self, widget, data=None): self.ui.response(Gtk.ResponseType.CANCEL) self.hide() def valid(self): return True autokey-0.90.4/src/lib/gtkui/popupmenu.py0000664000175000017500000001163211750175146017365 0ustar chrischris# -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import time, logging from gi.repository import Gtk, Gdk from autokey.configmanager import * from autokey.model import Folder # TODO remove later _logger = logging.getLogger("phrase-menu") class PopupMenu(Gtk.Menu): """ A popup menu that allows the user to select a phrase. """ def __init__(self, service, folders=[], items=[], onDesktop=True, title=None): Gtk.Menu.__init__(self) #self.set_take_focus(ConfigManager.SETTINGS[MENU_TAKES_FOCUS]) self.__i = 1 self.service = service if ConfigManager.SETTINGS[SORT_BY_USAGE_COUNT]: _logger.debug("Sorting phrase menu by usage count") folders.sort(key=lambda obj: obj.usageCount, reverse=True) items.sort(key=lambda obj: obj.usageCount, reverse=True) else: _logger.debug("Sorting phrase menu by item name/title") folders.sort(key=lambda obj: str(obj)) items.sort(key=lambda obj: str(obj)) if len(folders) == 1 and len(items) == 0 and onDesktop: # Only one folder - create menu with just its folders and items for folder in folders[0].folders: menuItem = Gtk.MenuItem(label=self.__getMnemonic(folder.title, onDesktop)) menuItem.set_submenu(PopupMenu(service, folder.folders, folder.items, onDesktop)) menuItem.set_use_underline(True) self.append(menuItem) if len(folders[0].folders) > 0: self.append(Gtk.SeparatorMenuItem()) self.__addItemsToSelf(folders[0].items, service, onDesktop) else: # Create phrase folder section for folder in folders: menuItem = Gtk.MenuItem(label=self.__getMnemonic(folder.title, onDesktop)) menuItem.set_submenu(PopupMenu(service, folder.folders, folder.items, False)) menuItem.set_use_underline(True) self.append(menuItem) if len(folders) > 0: self.append(Gtk.SeparatorMenuItem()) self.__addItemsToSelf(items, service, onDesktop) self.show_all() def __getMnemonic(self, desc, onDesktop): if 1 < 10 and '_' not in desc and onDesktop: ret = "_%d - %s" % (self.__i, desc) self.__i += 1 return ret else: return desc def show_on_desktop(self): Gdk.threads_enter() time.sleep(0.2) self.popup(None, None, None, None, 1, 0) Gdk.threads_leave() def remove_from_desktop(self): Gdk.threads_enter() self.popdown() Gdk.threads_leave() def __addItemsToSelf(self, items, service, onDesktop): # Create phrase section if ConfigManager.SETTINGS[SORT_BY_USAGE_COUNT]: items.sort(key=lambda obj: obj.usageCount, reverse=True) else: items.sort(key=lambda obj: str(obj)) i = 1 for item in items: #if onDesktop: # menuItem = Gtk.MenuItem(item.get_description(service.lastStackState), False) #else: menuItem = Gtk.MenuItem(label=self.__getMnemonic(item.description, onDesktop)) menuItem.connect("activate", self.__itemSelected, item) menuItem.set_use_underline(True) self.append(menuItem) def __itemSelected(self, widget, item): self.service.item_selected(item) # Testing stuff - remove later ---- class MockPhrase: def __init__(self, description): self.description = description class MockExpansionService: def phrase_selected(self, event, phrase): print phrase.description if __name__ == "__main__": Gdk.threads_init() myFolder = PhraseFolder("Some phrases") myFolder.add_phrase(MockPhrase("phrase 1")) myFolder.add_phrase(MockPhrase("phrase 2")) myFolder.add_phrase(MockPhrase("phrase 3")) myPhrases = [] myPhrases.append(MockPhrase("phrase 1")) myPhrases.append(MockPhrase("phrase 2")) myPhrases.append(MockPhrase("phrase 3")) menu = PhraseMenu(MockExpansionService(), [myFolder], myPhrases) menu.show_on_desktop() Gtk.main() autokey-0.90.4/config/ubuntu-mono-light/autokey-status.svg0000775000175000017500000000670011710765364022676 0ustar chrischris image/svg+xml autokey-0.90.4/config/ubuntu-mono-light/autokey-status-error.svg0000775000175000017500000000670011710765364024025 0ustar chrischris image/svg+xml autokey-0.90.4/config/Humanity/autokey-status.svg0000775000175000017500000000517011710765364021077 0ustar chrischris image/svg+xml autokey-0.90.4/config/Humanity/autokey-status-error.svg0000775000175000017500000000517011710765364022226 0ustar chrischris image/svg+xml autokey-0.90.4/config/ubuntu-mono-dark/autokey-status-error.svg0000775000175000017500000000670011710765364023637 0ustar chrischris image/svg+xml autokey-0.90.4/config/ubuntu-mono-dark/autokey-status.svg0000775000175000017500000000670011710765364022510 0ustar chrischris image/svg+xml autokey-0.90.4/doc/man/autokey-qt.10000664000175000017500000000373011723375735016016 0ustar chrischris.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH AUTOKEY-QT "1" "August 19, 2009" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME autokey-qt \- keyboard automation utility for KDE and QT .SH SYNOPSIS .B autokey-qt .RI [ options ] .SH DESCRIPTION This manual page briefly documents the .B autokey-qt command. .PP .\" TeX users may be more comfortable with the \fB\fP and .\" \fI\fP escape sequences to invode bold face and italics, .\" respectively. \fBautokey-qt\fP AutoKey is a desktop automation utility for Linux and X11. It allows the automation of virtually any task by responding to typed abbreviations and hotkeys. It offers a full-featured GUI that makes it highly accessible for novices, as well as a scripting interface offering the full flexibility and power of the Python language. .br For more information refer to the online wiki at: http://code.google.com/p/autokey/w/list .SH OPTIONS This program follows the usual GNU command line syntax, with long options starting with two dashes (`-'). A summary of options is included below. .TP .B \-\-help Show summary of options. .TP .B \-l, \-\-verbose Enable verbose (debug) logging. .TP .B \-c, \-\-configure Show the configuration window on startup, even if this is not the first run. .SH AUTHOR AutoKey was written by Chris Dekter, loosely based on a script by Sam Peterson. .PP This manual page was written by Chris Dekter . autokey-0.90.4/doc/man/autokey-run.10000664000175000017500000000343611662107715016172 0ustar chrischris.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH AUTOKEY-GTK "1" "August 19, 2009" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME autokey-run \- command-line execution utility for AutoKey .SH SYNOPSIS .B autokey-run .RI -[s|p|f] [name] .SH DESCRIPTION This manual page briefly documents the .B autokey-run command. .PP .\" TeX users may be more comfortable with the \fB\fP and .\" \fI\fP escape sequences to invode bold face and italics, .\" respectively. \fBautokey-run\fP A command-line execution utility for AutoKey, autokey-run allows you to initiate execution of phrases, scripts or folders from the command line. .br For more information refer to the online wiki at: http://code.google.com/p/autokey/w/list .SH OPTIONS This program follows the usual GNU command line syntax, with long options starting with two dashes (`-'). A summary of options is included below. .TP .B \-\-help Show summary of options. .TP .B \-l, \-\-script [name] Run a script with the specified name. .TP .B \-c, \-\-phrase [name] Paste a phrase with the specified name. .TP .B \-c, \-\-folder [name] Display a popup menu for the specified folder. .SH AUTHOR Chris Dekter. .PP This manual page was written by Chris Dekter . autokey-0.90.4/doc/man/autokey-gtk.10000664000175000017500000000374011354567042016152 0ustar chrischris.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH AUTOKEY-GTK "1" "August 19, 2009" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME autokey-gtk \- keyboard automation utility for GNOME and GTK .SH SYNOPSIS .B autokey-gtk .RI [ options ] .SH DESCRIPTION This manual page briefly documents the .B autokey-gtk command. .PP .\" TeX users may be more comfortable with the \fB\fP and .\" \fI\fP escape sequences to invode bold face and italics, .\" respectively. \fBautokey-gtk\fP AutoKey is a desktop automation utility for Linux and X11. It allows the automation of virtually any task by responding to typed abbreviations and hotkeys. It offers a full-featured GUI that makes it highly accessible for novices, as well as a scripting interface offering the full flexibility and power of the Python language. .br For more information refer to the online wiki at: http://code.google.com/p/autokey/w/list .SH OPTIONS This program follows the usual GNU command line syntax, with long options starting with two dashes (`-'). A summary of options is included below. .TP .B \-\-help Show summary of options. .TP .B \-l, \-\-verbose Enable verbose (debug) logging. .TP .B \-c, \-\-configure Show the configuration window on startup, even if this is not the first run. .SH AUTHOR AutoKey was written by Chris Dekter, loosely based on a script by Sam Peterson. .PP This manual page was written by Chris Dekter . autokey-0.90.4/doc/scripting/lib.scripting.Mouse-class.html0000664000175000017500000003424511744235773022714 0ustar chrischris lib.scripting.Mouse
Package lib :: Module scripting :: Class Mouse
[hide private]
[frames] | no frames]

Class Mouse

source code

Provides access to send mouse clicks

Instance Methods [hide private]
 
__init__(self, mediator) source code
 
click_relative(self, x, y, button)
Send a mouse click relative to the active window
source code
 
click_relative_self(self, x, y, button)
Send a mouse click relative to the current mouse position
source code
 
click_absolute(self, x, y, button)
Send a mouse click relative to the screen (absolute)
source code
 
wait_for_click(self, button, timeOut=10.0)
Wait for a mouse click
source code
Method Details [hide private]

click_relative(self, x, y, button)

source code 

Send a mouse click relative to the active window

Usage: mouse.click_relative(x, y, button)

Parameters:
  • x - x-coordinate in pixels, relative to upper left corner of window
  • y - y-coordinate in pixels, relative to upper left corner of window
  • button - mouse button to simulate (left=1, middle=2, right=3)

click_relative_self(self, x, y, button)

source code 

Send a mouse click relative to the current mouse position

Usage: mouse.click_relative_self(x, y, button)

Parameters:
  • x - x-offset in pixels, relative to current mouse position
  • y - y-offset in pixels, relative to current mouse position
  • button - mouse button to simulate (left=1, middle=2, right=3)

click_absolute(self, x, y, button)

source code 

Send a mouse click relative to the screen (absolute)

Usage: mouse.click_absolute(x, y, button)

Parameters:
  • x - x-coordinate in pixels, relative to upper left corner of window
  • y - y-coordinate in pixels, relative to upper left corner of window
  • button - mouse button to simulate (left=1, middle=2, right=3)

wait_for_click(self, button, timeOut=10.0)

source code 

Wait for a mouse click

Usage: mouse.wait_for_click(self, button, timeOut=10.0)

Parameters:
  • button - they mouse button click to wait for as a button number, 1-9
  • timeOut - maximum time, in seconds, to wait for the keypress to occur

autokey-0.90.4/doc/scripting/redirect.html0000664000175000017500000000240111724374236017535 0ustar chrischrisEpydoc Redirect Page

Epydoc Auto-redirect page

When javascript is enabled, this page will redirect URLs of the form redirect.html#dotted.name to the documentation for the object with the given fully-qualified dotted name.

 

autokey-0.90.4/doc/scripting/class-tree.html0000664000175000017500000001416211744235773020011 0ustar chrischris Class Hierarchy
 
[hide private]
[frames] | no frames]
[ Module Hierarchy | Class Hierarchy ]

Class Hierarchy

  • lib.scripting.Engine: Provides access to the internals of AutoKey.
  • lib.scripting.GtkClipboard: Read/write access to the X selection and clipboard - GTK version
  • lib.scripting.GtkDialog: Provides a simple interface for the display of some basic dialogs to collect information from the user.
  • lib.scripting.Keyboard: Provides access to the keyboard for event generation.
  • lib.scripting.Mouse: Provides access to send mouse clicks
  • lib.scripting.QtClipboard: Read/write access to the X selection and clipboard - QT version
  • lib.scripting.QtDialog: Provides a simple interface for the display of some basic dialogs to collect information from the user.
  • lib.scripting.System: Simplified access to some system commands.
  • lib.scripting.Window: Basic window management using wmctrl
  • object: The most base type
    • dict: dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list.
autokey-0.90.4/doc/scripting/lib.scripting-pysrc.html0000664000175000017500000112306711744235773021662 0ustar chrischris lib.scripting
Package lib :: Module scripting
[hide private]
[frames] | no frames]

Source Code for Module lib.scripting

   1  # -*- coding: utf-8 -*- 
   2   
   3  # Copyright (C) 2011 Chris Dekter 
   4  # 
   5  # This program is free software: you can redistribute it and/or modify 
   6  # it under the terms of the GNU General Public License as published by 
   7  # the Free Software Foundation, either version 3 of the License, or 
   8  # (at your option) any later version. 
   9  # 
  10  # This program is distributed in the hope that it will be useful, 
  11  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
  12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  13  # GNU General Public License for more details. 
  14  # 
  15  # You should have received a copy of the GNU General Public License 
  16  # along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  17   
  18  import subprocess, threading, time, re 
  19  import common#, model, iomediator 
  20  #if common.USING_QT: 
  21  #    from PyQt4.QtGui import QClipboard, QApplication 
  22  #else: 
  23  #    from gi.repository import Gtk, Gdk 
  24   
25 -class Keyboard:
26 """ 27 Provides access to the keyboard for event generation. 28 """ 29
30 - def __init__(self, mediator):
31 self.mediator = mediator
32
33 - def send_keys(self, keyString):
34 """ 35 Send a sequence of keys via keyboard events 36 37 Usage: C{keyboard.send_keys(keyString)} 38 39 @param keyString: string of keys (including special keys) to send 40 """ 41 self.mediator.interface.begin_send() 42 self.mediator.send_string(keyString.decode("utf-8")) 43 self.mediator.interface.finish_send()
44
45 - def send_key(self, key, repeat=1):
46 """ 47 Send a keyboard event 48 49 Usage: C{keyboard.send_key(key, repeat=1)} 50 51 @param key: they key to be sent (e.g. "s" or "<enter>") 52 @param repeat: number of times to repeat the key event 53 """ 54 for x in xrange(repeat): 55 self.mediator.send_key(key.decode("utf-8")) 56 self.mediator.flush()
57
58 - def press_key(self, key):
59 """ 60 Send a key down event 61 62 Usage: C{keyboard.press_key(key)} 63 64 The key will be treated as down until a matching release_key() is sent. 65 @param key: they key to be pressed (e.g. "s" or "<enter>") 66 """ 67 self.mediator.press_key(key.decode("utf-8"))
68
69 - def release_key(self, key):
70 """ 71 Send a key up event 72 73 Usage: C{keyboard.release_key(key)} 74 75 If the specified key was not made down using press_key(), the event will be 76 ignored. 77 @param key: they key to be released (e.g. "s" or "<enter>") 78 """ 79 self.mediator.release_key(key.decode("utf-8"))
80
81 - def fake_keypress(self, key, repeat=1):
82 """ 83 Fake a keypress 84 85 Usage: C{keyboard.fake_keypress(key, repeat=1)} 86 87 Uses XTest to 'fake' a keypress. This is useful to send keypresses to some 88 applications which won't respond to keyboard.send_key() 89 90 @param key: they key to be sent (e.g. "s" or "<enter>") 91 @param repeat: number of times to repeat the key event 92 """ 93 for x in xrange(repeat): 94 self.mediator.fake_keypress(key.decode("utf-8"))
95
96 - def wait_for_keypress(self, key, modifiers=[], timeOut=10.0):
97 """ 98 Wait for a keypress or key combination 99 100 Usage: C{keyboard.wait_for_keypress(self, key, modifiers=[], timeOut=10.0)} 101 102 Note: this function cannot be used to wait for modifier keys on their own 103 104 @param key: they key to wait for 105 @param modifiers: list of modifiers that should be pressed with the key 106 @param timeOut: maximum time, in seconds, to wait for the keypress to occur 107 """ 108 w = iomediator.Waiter(key, modifiers, None, timeOut) 109 w.wait()
110 111
112 -class Mouse:
113 """ 114 Provides access to send mouse clicks 115 """
116 - def __init__(self, mediator):
117 self.mediator = mediator
118
119 - def click_relative(self, x, y, button):
120 """ 121 Send a mouse click relative to the active window 122 123 Usage: C{mouse.click_relative(x, y, button)} 124 125 @param x: x-coordinate in pixels, relative to upper left corner of window 126 @param y: y-coordinate in pixels, relative to upper left corner of window 127 @param button: mouse button to simulate (left=1, middle=2, right=3) 128 """ 129 self.mediator.send_mouse_click(x, y, button, True)
130
131 - def click_relative_self(self, x, y, button):
132 """ 133 Send a mouse click relative to the current mouse position 134 135 Usage: C{mouse.click_relative_self(x, y, button)} 136 137 @param x: x-offset in pixels, relative to current mouse position 138 @param y: y-offset in pixels, relative to current mouse position 139 @param button: mouse button to simulate (left=1, middle=2, right=3) 140 """ 141 self.mediator.send_mouse_click_relative(x, y, button)
142
143 - def click_absolute(self, x, y, button):
144 """ 145 Send a mouse click relative to the screen (absolute) 146 147 Usage: C{mouse.click_absolute(x, y, button)} 148 149 @param x: x-coordinate in pixels, relative to upper left corner of window 150 @param y: y-coordinate in pixels, relative to upper left corner of window 151 @param button: mouse button to simulate (left=1, middle=2, right=3) 152 """ 153 self.mediator.send_mouse_click(x, y, button, False)
154
155 - def wait_for_click(self, button, timeOut=10.0):
156 """ 157 Wait for a mouse click 158 159 Usage: C{mouse.wait_for_click(self, button, timeOut=10.0)} 160 161 @param button: they mouse button click to wait for as a button number, 1-9 162 @param timeOut: maximum time, in seconds, to wait for the keypress to occur 163 """ 164 button = int(button) 165 w = iomediator.Waiter(None, None, button, timeOut) 166 w.wait()
167 168
169 -class Store(dict):
170 """ 171 Allows persistent storage of values between invocations of the script. 172 """ 173
174 - def set_value(self, key, value):
175 """ 176 Store a value 177 178 Usage: C{store.set_value(key, value)} 179 """ 180 self[key] = value
181
182 - def get_value(self, key):
183 """ 184 Get a value 185 186 Usage: C{store.get_value(key)} 187 """ 188 return self[key]
189
190 - def remove_value(self, key):
191 """ 192 Remove a value 193 194 Usage: C{store.remove_value(key)} 195 """ 196 del self[key]
197
198 - def set_global_value(self, key, value):
199 """ 200 Store a global value 201 202 Usage: C{store.set_global_value(key, value)} 203 204 The value stored with this method will be available to all scripts. 205 """ 206 Store.GLOBALS[key] = value
207
208 - def get_global_value(self, key):
209 """ 210 Get a global value 211 212 Usage: C{store.get_global_value(key)} 213 """ 214 return self.GLOBALS[key]
215
216 - def remove_global_value(self, key):
217 """ 218 Remove a global value 219 220 Usage: C{store.remove_global_value(key)} 221 """ 222 del self.GLOBALS[key]
223 224
225 -class QtDialog:
226 """ 227 Provides a simple interface for the display of some basic dialogs to collect information from the user. 228 229 This version uses KDialog to integrate well with KDE. To pass additional arguments to KDialog that are 230 not specifically handled, use keyword arguments. For example, to pass the --geometry argument to KDialog 231 to specify the desired size of the dialog, pass C{geometry="700x400"} as one of the parameters. All 232 keyword arguments must be given as strings. 233 234 A note on exit codes: an exit code of 0 indicates that the user clicked OK. 235 """ 236
237 - def __runKdialog(self, title, args, kwargs):
238 for k, v in kwargs.iteritems(): 239 args.append("--" + k) 240 args.append(v) 241 242 p = subprocess.Popen(["kdialog", "--title", title] + args, stdout=subprocess.PIPE) 243 retCode = p.wait() 244 output = p.stdout.read()[:-1] # Drop trailing newline 245 246 return (retCode, output)
247
248 - def info_dialog(self, title="Information", message="", **kwargs):
249 """ 250 Show an information dialog 251 252 Usage: C{dialog.info_dialog(title="Information", message="", **kwargs)} 253 254 @param title: window title for the dialog 255 @param message: message displayed in the dialog 256 @return: a tuple containing the exit code and user input 257 @rtype: C{tuple(int, str)} 258 """ 259 return self.__runKdialog(title, ["--msgbox", message], kwargs)
260
261 - def input_dialog(self, title="Enter a value", message="Enter a value", default="", **kwargs):
262 """ 263 Show an input dialog 264 265 Usage: C{dialog.input_dialog(title="Enter a value", message="Enter a value", default="", **kwargs)} 266 267 @param title: window title for the dialog 268 @param message: message displayed above the input box 269 @param default: default value for the input box 270 @return: a tuple containing the exit code and user input 271 @rtype: C{tuple(int, str)} 272 """ 273 return self.__runKdialog(title, ["--inputbox", message, default], kwargs)
274
275 - def password_dialog(self, title="Enter password", message="Enter password", **kwargs):
276 """ 277 Show a password input dialog 278 279 Usage: C{dialog.password_dialog(title="Enter password", message="Enter password", **kwargs)} 280 281 @param title: window title for the dialog 282 @param message: message displayed above the password input box 283 @return: a tuple containing the exit code and user input 284 @rtype: C{tuple(int, str)} 285 """ 286 return self.__runKdialog(title, ["--password", message], kwargs)
287
288 - def combo_menu(self, options, title="Choose an option", message="Choose an option", **kwargs):
289 """ 290 Show a combobox menu 291 292 Usage: C{dialog.combo_menu(options, title="Choose an option", message="Choose an option", **kwargs)} 293 294 @param options: list of options (strings) for the dialog 295 @param title: window title for the dialog 296 @param message: message displayed above the combobox 297 @return: a tuple containing the exit code and user choice 298 @rtype: C{tuple(int, str)} 299 """ 300 return self.__runKdialog(title, ["--combobox", message] + options, kwargs)
301
302 - def list_menu(self, options, title="Choose a value", message="Choose a value", default=None, **kwargs):
303 """ 304 Show a single-selection list menu 305 306 Usage: C{dialog.list_menu(options, title="Choose a value", message="Choose a value", default=None, **kwargs)} 307 308 @param options: list of options (strings) for the dialog 309 @param title: window title for the dialog 310 @param message: message displayed above the list 311 @param default: default value to be selected 312 @return: a tuple containing the exit code and user choice 313 @rtype: C{tuple(int, str)} 314 """ 315 316 choices = [] 317 optionNum = 0 318 for option in options: 319 choices.append(str(optionNum)) 320 choices.append(option) 321 if option == default: 322 choices.append("on") 323 else: 324 choices.append("off") 325 optionNum += 1 326 327 retCode, result = self.__runKdialog(title, ["--radiolist", message] + choices, kwargs) 328 choice = options[int(result)] 329 330 return retCode, choice
331
332 - def list_menu_multi(self, options, title="Choose one or more values", message="Choose one or more values", defaults=[], **kwargs):
333 """ 334 Show a multiple-selection list menu 335 336 Usage: C{dialog.list_menu_multi(options, title="Choose one or more values", message="Choose one or more values", defaults=[], **kwargs)} 337 338 @param options: list of options (strings) for the dialog 339 @param title: window title for the dialog 340 @param message: message displayed above the list 341 @param defaults: list of default values to be selected 342 @return: a tuple containing the exit code and user choice 343 @rtype: C{tuple(int, str)} 344 """ 345 346 choices = [] 347 optionNum = 0 348 for option in options: 349 choices.append(str(optionNum)) 350 choices.append(option) 351 if option in defaults: 352 choices.append("on") 353 else: 354 choices.append("off") 355 optionNum += 1 356 357 retCode, output = self.__runKdialog(title, ["--separate-output", "--checklist", message] + choices, kwargs) 358 results = output.split() 359 360 choices = [] 361 for index in results: 362 choices.append(options[int(index)]) 363 364 return retCode, choices
365
366 - def open_file(self, title="Open File", initialDir="~", fileTypes="*|All Files", rememberAs=None, **kwargs):
367 """ 368 Show an Open File dialog 369 370 Usage: C{dialog.open_file(title="Open File", initialDir="~", fileTypes="*|All Files", rememberAs=None, **kwargs)} 371 372 @param title: window title for the dialog 373 @param initialDir: starting directory for the file dialog 374 @param fileTypes: file type filter expression 375 @param rememberAs: gives an ID to this file dialog, allowing it to open at the last used path next time 376 @return: a tuple containing the exit code and file path 377 @rtype: C{tuple(int, str)} 378 """ 379 if rememberAs is not None: 380 return self.__runKdialog(title, ["--getopenfilename", initialDir, fileTypes, ":" + rememberAs], kwargs) 381 else: 382 return self.__runKdialog(title, ["--getopenfilename", initialDir, fileTypes], kwargs)
383
384 - def save_file(self, title="Save As", initialDir="~", fileTypes="*|All Files", rememberAs=None, **kwargs):
385 """ 386 Show a Save As dialog 387 388 Usage: C{dialog.save_file(title="Save As", initialDir="~", fileTypes="*|All Files", rememberAs=None, **kwargs)} 389 390 @param title: window title for the dialog 391 @param initialDir: starting directory for the file dialog 392 @param fileTypes: file type filter expression 393 @param rememberAs: gives an ID to this file dialog, allowing it to open at the last used path next time 394 @return: a tuple containing the exit code and file path 395 @rtype: C{tuple(int, str)} 396 """ 397 if rememberAs is not None: 398 return self.__runKdialog(title, ["--getsavefilename", initialDir, fileTypes, ":" + rememberAs], kwargs) 399 else: 400 return self.__runKdialog(title, ["--getsavefilename", initialDir, fileTypes], kwargs)
401
402 - def choose_directory(self, title="Select Directory", initialDir="~", rememberAs=None, **kwargs):
403 """ 404 Show a Directory Chooser dialog 405 406 Usage: C{dialog.choose_directory(title="Select Directory", initialDir="~", rememberAs=None, **kwargs)} 407 408 @param title: window title for the dialog 409 @param initialDir: starting directory for the directory chooser dialog 410 @param rememberAs: gives an ID to this file dialog, allowing it to open at the last used path next time 411 @return: a tuple containing the exit code and chosen path 412 @rtype: C{tuple(int, str)} 413 """ 414 if rememberAs is not None: 415 return self.__runKdialog(title, ["--getexistingdirectory", initialDir, ":" + rememberAs], kwargs) 416 else: 417 return self.__runKdialog(title, ["--getexistingdirectory", initialDir], kwargs)
418
419 - def choose_colour(self, title="Select Colour", **kwargs):
420 """ 421 Show a Colour Chooser dialog 422 423 Usage: C{dialog.choose_colour(title="Select Colour")} 424 425 @param title: window title for the dialog 426 @return: a tuple containing the exit code and colour 427 @rtype: C{tuple(int, str)} 428 """ 429 return self.__runKdialog(title, ["--getcolor"], kwargs)
430
431 - def calendar(self, title="Choose a date", format="%Y-%m-%d", date="today", **kwargs):
432 """ 433 Show a calendar dialog 434 435 Usage: C{dialog.calendar_dialog(title="Choose a date", format="%Y-%m-%d", date="YYYY-MM-DD", **kwargs)} 436 437 Note: the format and date parameters are not currently used 438 439 @param title: window title for the dialog 440 @param format: format of date to be returned 441 @param date: initial date as YYYY-MM-DD, otherwise today 442 @return: a tuple containing the exit code and date 443 @rtype: C{tuple(int, str)} 444 """ 445 return self.__runKdialog(title, ["--calendar"], kwargs)
446 447
448 -class System:
449 """ 450 Simplified access to some system commands. 451 """ 452
453 - def exec_command(self, command, getOutput=True):
454 """ 455 Execute a shell command 456 457 Usage: C{system.exec_command(command, getOutput=True)} 458 459 Set getOutput to False if the command does not exit and return immediately. Otherwise 460 AutoKey will not respond to any hotkeys/abbreviations etc until the process started 461 by the command exits. 462 463 @param command: command to be executed (including any arguments) - e.g. "ls -l" 464 @param getOutput: whether to capture the (stdout) output of the command 465 @raise subprocess.CalledProcessError: if the command returns a non-zero exit code 466 """ 467 if getOutput: 468 p = subprocess.Popen(command, shell=True, bufsize=-1, stdout=subprocess.PIPE) 469 retCode = p.wait() 470 output = p.stdout.read()[:-1] 471 if retCode != 0: 472 raise subprocess.CalledProcessError(retCode, output) 473 else: 474 return output 475 else: 476 subprocess.Popen(command, shell=True, bufsize=-1)
477
478 - def create_file(self, fileName, contents=""):
479 """ 480 Create a file with contents 481 482 Usage: C{system.create_file(fileName, contents="")} 483 484 @param fileName: full path to the file to be created 485 @param contents: contents to insert into the file 486 """ 487 f = open(fileName, "w") 488 f.write(contents) 489 f.close()
490 491
492 -class GtkDialog:
493 """ 494 Provides a simple interface for the display of some basic dialogs to collect information from the user. 495 496 This version uses Zenity to integrate well with GNOME. To pass additional arguments to Zenity that are 497 not specifically handled, use keyword arguments. For example, to pass the --timeout argument to Zenity 498 pass C{timeout="15"} as one of the parameters. All keyword arguments must be given as strings. 499 500 A note on exit codes: an exit code of 0 indicates that the user clicked OK. 501 """ 502
503 - def __runZenity(self, title, args, kwargs):
504 for k, v in kwargs.iteritems(): 505 args.append("--" + k) 506 args.append(v) 507 508 p = subprocess.Popen(["zenity", "--title", title] + args, stdout=subprocess.PIPE) 509 retCode = p.wait() 510 output = p.stdout.read()[:-1] # Drop trailing newline 511 512 return (retCode, output)
513
514 - def info_dialog(self, title="Information", message="", **kwargs):
515 """ 516 Show an information dialog 517 518 Usage: C{dialog.info_dialog(title="Information", message="", **kwargs)} 519 520 @param title: window title for the dialog 521 @param message: message displayed in the dialog 522 @return: a tuple containing the exit code and user input 523 @rtype: C{tuple(int, str)} 524 """ 525 return self.__runZenity(title, ["--info", "--text", message], kwargs)
526
527 - def input_dialog(self, title="Enter a value", message="Enter a value", default="", **kwargs):
528 """ 529 Show an input dialog 530 531 Usage: C{dialog.input_dialog(title="Enter a value", message="Enter a value", default="", **kwargs)} 532 533 @param title: window title for the dialog 534 @param message: message displayed above the input box 535 @param default: default value for the input box 536 @return: a tuple containing the exit code and user input 537 @rtype: C{tuple(int, str)} 538 """ 539 return self.__runZenity(title, ["--entry", "--text", message, "--entry-text", default], kwargs)
540
541 - def password_dialog(self, title="Enter password", message="Enter password", **kwargs):
542 """ 543 Show a password input dialog 544 545 Usage: C{dialog.password_dialog(title="Enter password", message="Enter password")} 546 547 @param title: window title for the dialog 548 @param message: message displayed above the password input box 549 @return: a tuple containing the exit code and user input 550 @rtype: C{tuple(int, str)} 551 """ 552 return self.__runZenity(title, ["--entry", "--text", message, "--hide-text"], kwargs) 553 554 #def combo_menu(self, options, title="Choose an option", message="Choose an option"): 555 """ 556 Show a combobox menu - not supported by zenity 557 558 Usage: C{dialog.combo_menu(options, title="Choose an option", message="Choose an option")} 559 560 @param options: list of options (strings) for the dialog 561 @param title: window title for the dialog 562 @param message: message displayed above the combobox 563 """
564 #return self.__runZenity(title, ["--combobox", message] + options) 565
566 - def list_menu(self, options, title="Choose a value", message="Choose a value", default=None, **kwargs):
567 """ 568 Show a single-selection list menu 569 570 Usage: C{dialog.list_menu(options, title="Choose a value", message="Choose a value", default=None, **kwargs)} 571 572 @param options: list of options (strings) for the dialog 573 @param title: window title for the dialog 574 @param message: message displayed above the list 575 @param default: default value to be selected 576 @return: a tuple containing the exit code and user choice 577 @rtype: C{tuple(int, str)} 578 """ 579 580 choices = [] 581 #optionNum = 0 582 for option in options: 583 if option == default: 584 choices.append("TRUE") 585 else: 586 choices.append("FALSE") 587 588 #choices.append(str(optionNum)) 589 choices.append(option) 590 #optionNum += 1 591 592 return self.__runZenity(title, ["--list", "--radiolist", "--text", message, "--column", " ", "--column", "Options"] + choices, kwargs)
593 594 #return retCode, choice 595
596 - def list_menu_multi(self, options, title="Choose one or more values", message="Choose one or more values", defaults=[], **kwargs):
597 """ 598 Show a multiple-selection list menu 599 600 Usage: C{dialog.list_menu_multi(options, title="Choose one or more values", message="Choose one or more values", defaults=[], **kwargs)} 601 602 @param options: list of options (strings) for the dialog 603 @param title: window title for the dialog 604 @param message: message displayed above the list 605 @param defaults: list of default values to be selected 606 @return: a tuple containing the exit code and user choice 607 @rtype: C{tuple(int, str)} 608 """ 609 610 choices = [] 611 #optionNum = 0 612 for option in options: 613 if option in defaults: 614 choices.append("TRUE") 615 else: 616 choices.append("FALSE") 617 618 #choices.append(str(optionNum)) 619 choices.append(option) 620 #optionNum += 1 621 622 retCode, output = self.__runZenity(title, ["--list", "--checklist", "--text", message, "--column", " ", "--column", "Options"] + choices, kwargs) 623 results = output.split('|') 624 625 #choices = [] 626 #for choice in results: 627 # choices.append(choice) 628 629 return retCode, results
630
631 - def open_file(self, title="Open File", **kwargs):
632 """ 633 Show an Open File dialog 634 635 Usage: C{dialog.open_file(title="Open File", **kwargs)} 636 637 @param title: window title for the dialog 638 @return: a tuple containing the exit code and file path 639 @rtype: C{tuple(int, str)} 640 """ 641 #if rememberAs is not None: 642 # return self.__runZenity(title, ["--getopenfilename", initialDir, fileTypes, ":" + rememberAs]) 643 #else: 644 return self.__runZenity(title, ["--file-selection"], kwargs)
645
646 - def save_file(self, title="Save As", **kwargs):
647 """ 648 Show a Save As dialog 649 650 Usage: C{dialog.save_file(title="Save As", **kwargs)} 651 652 @param title: window title for the dialog 653 @return: a tuple containing the exit code and file path 654 @rtype: C{tuple(int, str)} 655 """ 656 #if rememberAs is not None: 657 # return self.__runZenity(title, ["--getsavefilename", initialDir, fileTypes, ":" + rememberAs]) 658 #else: 659 return self.__runZenity(title, ["--file-selection", "--save"], kwargs)
660
661 - def choose_directory(self, title="Select Directory", initialDir="~", **kwargs):
662 """ 663 Show a Directory Chooser dialog 664 665 Usage: C{dialog.choose_directory(title="Select Directory", **kwargs)} 666 667 @param title: window title for the dialog 668 @return: a tuple containing the exit code and path 669 @rtype: C{tuple(int, str)} 670 """ 671 #if rememberAs is not None: 672 # return self.__runZenity(title, ["--getexistingdirectory", initialDir, ":" + rememberAs]) 673 #else: 674 return self.__runZenity(title, ["--file-selection", "--directory"], kwargs) 675 676 #def choose_colour(self, title="Select Colour"): 677 """ 678 Show a Colour Chooser dialog - not supported by zenity 679 680 Usage: C{dialog.choose_colour(title="Select Colour")} 681 682 @param title: window title for the dialog 683 """
684 #return self.__runZenity(title, ["--getcolor"]) 685
686 - def calendar(self, title="Choose a date", format="%Y-%m-%d", date="today", **kwargs):
687 """ 688 Show a calendar dialog 689 690 Usage: C{dialog.calendar_dialog(title="Choose a date", format="%Y-%m-%d", date="YYYY-MM-DD", **kwargs)} 691 692 @param title: window title for the dialog 693 @param format: format of date to be returned 694 @param date: initial date as YYYY-MM-DD, otherwise today 695 @return: a tuple containing the exit code and date 696 @rtype: C{tuple(int, str)} 697 """ 698 if re.match(r"[0-9]{4}-[0-9]{2}-[0-9]{2}", date): 699 year = date[0:4] 700 month = date[5:7] 701 day = date[8:10] 702 date_args = ["--year=" + year, "--month=" + month, "--day=" + day] 703 else: 704 date_args = [] 705 return self.__runZenity(title, ["--calendar", "--date-format=" + format] + date_args, kwargs)
706 707
708 -class QtClipboard:
709 """ 710 Read/write access to the X selection and clipboard - QT version 711 """ 712
713 - def __init__(self, app):
714 self.clipBoard = QApplication.clipboard() 715 self.app = app
716
717 - def fill_selection(self, contents):
718 """ 719 Copy text into the X selection 720 721 Usage: C{clipboard.fill_selection(contents)} 722 723 @param contents: string to be placed in the selection 724 """ 725 self.__execAsync(self.__fillSelection, contents)
726
727 - def __fillSelection(self, string):
728 self.clipBoard.setText(string, QClipboard.Selection) 729 self.sem.release()
730
731 - def get_selection(self):
732 """ 733 Read text from the X selection 734 735 Usage: C{clipboard.get_selection()} 736 737 @return: text contents of the mouse selection 738 @rtype: C{str} 739 """ 740 self.__execAsync(self.__getSelection) 741 return unicode(self.text)
742
743 - def __getSelection(self):
744 self.text = self.clipBoard.text(QClipboard.Selection) 745 self.sem.release()
746
747 - def fill_clipboard(self, contents):
748 """ 749 Copy text into the clipboard 750 751 Usage: C{clipboard.fill_clipboard(contents)} 752 753 @param contents: string to be placed in the selection 754 """ 755 self.__execAsync(self.__fillClipboard, contents)
756
757 - def __fillClipboard(self, string):
758 self.clipBoard.setText(string, QClipboard.Clipboard) 759 self.sem.release()
760
761 - def get_clipboard(self):
762 """ 763 Read text from the clipboard 764 765 Usage: C{clipboard.get_clipboard()} 766 767 @return: text contents of the clipboard 768 @rtype: C{str} 769 """ 770 self.__execAsync(self.__getClipboard) 771 return unicode(self.text)
772
773 - def __getClipboard(self):
774 self.text = self.clipBoard.text(QClipboard.Clipboard) 775 self.sem.release()
776
777 - def __execAsync(self, callback, *args):
778 self.sem = threading.Semaphore(0) 779 self.app.exec_in_main(callback, *args) 780 self.sem.acquire()
781 782
783 -class GtkClipboard:
784 """ 785 Read/write access to the X selection and clipboard - GTK version 786 """ 787
788 - def __init__(self, app):
789 self.clipBoard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) 790 self.selection = Gtk.Clipboard.get(Gdk.SELECTION_PRIMARY) 791 self.app = app
792
793 - def fill_selection(self, contents):
794 """ 795 Copy text into the X selection 796 797 Usage: C{clipboard.fill_selection(contents)} 798 799 @param contents: string to be placed in the selection 800 """ 801 #self.__execAsync(self.__fillSelection, contents) 802 self.__fillSelection(contents)
803
804 - def __fillSelection(self, string):
805 Gdk.threads_enter() 806 self.selection.set_text(string.encode("utf-8")) 807 Gdk.threads_leave()
808 #self.sem.release() 809
810 - def get_selection(self):
811 """ 812 Read text from the X selection 813 814 Usage: C{clipboard.get_selection()} 815 816 @return: text contents of the mouse selection 817 @rtype: C{str} 818 @raise Exception: if no text was found in the selection 819 """ 820 self.__execAsync(self.selection.request_text, self.__receive) 821 if self.text is not None: 822 return self.text.decode("utf-8") 823 else: 824 raise Exception("No text found in X selection")
825
826 - def __receive(self, cb, text, data=None):
827 self.text = text 828 self.sem.release()
829
830 - def fill_clipboard(self, contents):
831 """ 832 Copy text into the clipboard 833 834 Usage: C{clipboard.fill_clipboard(contents)} 835 836 @param contents: string to be placed in the selection 837 """ 838 self.__fillClipboard(contents)
839
840 - def __fillClipboard(self, string):
841 Gdk.threads_enter() 842 self.clipBoard.set_text(string.encode("utf-8")) 843 Gdk.threads_leave()
844 #self.sem.release() 845
846 - def get_clipboard(self):
847 """ 848 Read text from the clipboard 849 850 Usage: C{clipboard.get_clipboard()} 851 852 @return: text contents of the clipboard 853 @rtype: C{str} 854 @raise Exception: if no text was found on the clipboard 855 """ 856 self.__execAsync(self.clipBoard.request_text, self.__receive) 857 if self.text is not None: 858 return self.text.decode("utf-8") 859 else: 860 raise Exception("No text found on clipboard")
861
862 - def __execAsync(self, callback, *args):
863 self.sem = threading.Semaphore(0) 864 Gdk.threads_enter() 865 callback(*args) 866 Gdk.threads_leave() 867 self.sem.acquire()
868 869
870 -class Window:
871 """ 872 Basic window management using wmctrl 873 874 Note: in all cases where a window title is required (with the exception of wait_for_focus()), 875 two special values of window title are permitted: 876 877 :ACTIVE: - select the currently active window 878 :SELECT: - select the desired window by clicking on it 879 """ 880
881 - def __init__(self, mediator):
882 self.mediator = mediator
883
884 - def wait_for_focus(self, title, timeOut=5):
885 """ 886 Wait for window with the given title to have focus 887 888 Usage: C{window.wait_for_focus(title, timeOut=5)} 889 890 If the window becomes active, returns True. Otherwise, returns False if 891 the window has not become active by the time the timeout has elapsed. 892 893 @param title: title to match against (as a regular expression) 894 @param timeOut: period (seconds) to wait before giving up 895 @rtype: boolean 896 """ 897 regex = re.compile(title) 898 waited = 0 899 while waited <= timeOut: 900 if regex.match(self.mediator.interface.get_window_title()): 901 return True 902 903 if timeOut == 0: 904 break # zero length timeout, if not matched go straight to end 905 906 time.sleep(0.3) 907 waited += 0.3 908 909 return False
910
911 - def wait_for_exist(self, title, timeOut=5):
912 """ 913 Wait for window with the given title to be created 914 915 Usage: C{window.wait_for_exist(title, timeOut=5)} 916 917 If the window is in existence, returns True. Otherwise, returns False if 918 the window has not been created by the time the timeout has elapsed. 919 920 @param title: title to match against (as a regular expression) 921 @param timeOut: period (seconds) to wait before giving up 922 @rtype: boolean 923 """ 924 regex = re.compile(title) 925 waited = 0 926 while waited <= timeOut: 927 retCode, output = self.__runWmctrl(["-l"]) 928 for line in output.split('\n'): 929 if regex.match(line[14:].split(' ', 1)[-1]): 930 return True 931 932 if timeOut == 0: 933 break # zero length timeout, if not matched go straight to end 934 935 time.sleep(0.3) 936 waited += 0.3 937 938 return False
939
940 - def activate(self, title, switchDesktop=False, matchClass=False):
941 """ 942 Activate the specified window, giving it input focus 943 944 Usage: C{window.activate(title, switchDesktop=False, matchClass=False)} 945 946 If switchDesktop is False (default), the window will be moved to the current desktop 947 and activated. Otherwise, switch to the window's current desktop and activate it there. 948 949 @param title: window title to match against (as case-insensitive substring match) 950 @param switchDesktop: whether or not to switch to the window's current desktop 951 @param matchClass: if True, match on the window class instead of the title 952 """ 953 if switchDesktop: 954 args = ["-a", title] 955 else: 956 args = ["-R", title] 957 if matchClass: 958 args += ["-x"] 959 self.__runWmctrl(args)
960
961 - def close(self, title, matchClass=False):
962 """ 963 Close the specified window gracefully 964 965 Usage: C{window.close(title, matchClass=False)} 966 967 @param title: window title to match against (as case-insensitive substring match) 968 @param matchClass: if True, match on the window class instead of the title 969 """ 970 if matchClass: 971 self.__runWmctrl(["-c", title, "-x"]) 972 else: 973 self.__runWmctrl(["-c", title])
974
975 - def resize_move(self, title, xOrigin=-1, yOrigin=-1, width=-1, height=-1, matchClass=False):
976 """ 977 Resize and/or move the specified window 978 979 Usage: C{window.close(title, xOrigin=-1, yOrigin=-1, width=-1, height=-1, matchClass=False)} 980 981 Leaving and of the position/dimension values as the default (-1) will cause that 982 value to be left unmodified. 983 984 @param title: window title to match against (as case-insensitive substring match) 985 @param xOrigin: new x origin of the window (upper left corner) 986 @param yOrigin: new y origin of the window (upper left corner) 987 @param width: new width of the window 988 @param height: new height of the window 989 @param matchClass: if True, match on the window class instead of the title 990 """ 991 mvArgs = ["0", str(xOrigin), str(yOrigin), str(width), str(height)] 992 if matchClass: 993 xArgs = ["-x"] 994 else: 995 xArgs = [] 996 self.__runWmctrl(["-r", title, "-e", ','.join(mvArgs)] + xArgs)
997 998
999 - def move_to_desktop(self, title, deskNum, matchClass=False):
1000 """ 1001 Move the specified window to the given desktop 1002 1003 Usage: C{window.move_to_desktop(title, deskNum, matchClass=False)} 1004 1005 @param title: window title to match against (as case-insensitive substring match) 1006 @param deskNum: desktop to move the window to (note: zero based) 1007 @param matchClass: if True, match on the window class instead of the title 1008 """ 1009 if matchClass: 1010 xArgs = ["-x"] 1011 else: 1012 xArgs = [] 1013 self.__runWmctrl(["-r", title, "-t", str(deskNum)] + xArgs)
1014 1015
1016 - def switch_desktop(self, deskNum):
1017 """ 1018 Switch to the specified desktop 1019 1020 Usage: C{window.switch_desktop(deskNum)} 1021 1022 @param deskNum: desktop to switch to (note: zero based) 1023 """ 1024 self.__runWmctrl(["-s", str(deskNum)])
1025
1026 - def set_property(self, title, action, prop, matchClass=False):
1027 """ 1028 Set a property on the given window using the specified action 1029 1030 Usage: C{window.set_property(title, action, prop, matchClass=False)} 1031 1032 Allowable actions: C{add, remove, toggle} 1033 Allowable properties: C{modal, sticky, maximized_vert, maximized_horz, shaded, skip_taskbar, 1034 skip_pager, hidden, fullscreen, above} 1035 1036 @param title: window title to match against (as case-insensitive substring match) 1037 @param action: one of the actions listed above 1038 @param prop: one of the properties listed above 1039 @param matchClass: if True, match on the window class instead of the title 1040 """ 1041 if matchClass: 1042 xArgs = ["-x"] 1043 else: 1044 xArgs = [] 1045 self.__runWmctrl(["-r", title, "-b" + action + ',' + prop] + xArgs)
1046
1047 - def get_active_geometry(self):
1048 """ 1049 Get the geometry of the currently active window 1050 1051 Usage: C{window.get_active_geometry()} 1052 1053 @return: a 4-tuple containing the x-origin, y-origin, width and height of the window (in pixels) 1054 @rtype: C{tuple(int, int, int, int)} 1055 """ 1056 active = self.mediator.interface.get_window_title() 1057 result, output = self.__runWmctrl(["-l", "-G"]) 1058 matchingLine = None 1059 for line in output.split('\n'): 1060 if active in line[34:].split(' ', 1)[-1]: 1061 matchingLine = line 1062 1063 if matchingLine is not None: 1064 output = matchingLine.split()[2:6] 1065 return map(int, output) 1066 else: 1067 return None
1068
1069 - def get_active_title(self):
1070 """ 1071 Get the visible title of the currently active window 1072 1073 Usage: C{window.get_active_title()} 1074 1075 @return: the visible title of the currentle active window 1076 @rtype: C{str} 1077 """ 1078 return self.mediator.interface.get_window_title()
1079
1080 - def get_active_class(self):
1081 """ 1082 Get the class of the currently active window 1083 1084 Usage: C{window.get_active_class()} 1085 1086 @return: the class of the currentle active window 1087 @rtype: C{str} 1088 """ 1089 return self.mediator.interface.get_window_class()
1090
1091 - def __runWmctrl(self, args):
1092 p = subprocess.Popen(["wmctrl"] + args, stdout=subprocess.PIPE) 1093 retCode = p.wait() 1094 output = p.stdout.read()[:-1] # Drop trailing newline 1095 1096 return (retCode, output)
1097 1098
1099 -class Engine:
1100 """ 1101 Provides access to the internals of AutoKey. 1102 1103 Note that any configuration changes made using this API while the configuration window 1104 is open will not appear until it is closed and re-opened. 1105 """ 1106
1107 - def __init__(self, configManager, runner):
1108 self.configManager = configManager 1109 self.runner = runner 1110 self.monitor = configManager.app.monitor 1111 self.__returnValue = ''
1112
1113 - def get_folder(self, title):
1114 """ 1115 Retrieve a folder by its title 1116 1117 Usage: C{engine.get_folder(title)} 1118 1119 Note that if more than one folder has the same title, only the first match will be 1120 returned. 1121 """ 1122 for folder in self.configManager.allFolders: 1123 if folder.title == title: 1124 return folder 1125 return None
1126
1127 - def create_phrase(self, folder, description, contents):
1128 """ 1129 Create a text phrase 1130 1131 Usage: C{engine.create_phrase(folder, description, contents)} 1132 1133 A new phrase with no abbreviation or hotkey is created in the specified folder 1134 1135 @param folder: folder to place the abbreviation in, retrieved using C{engine.get_folder()} 1136 @param description: description for the phrase 1137 @param contents: the expansion text 1138 """ 1139 self.monitor.suspend() 1140 p = model.Phrase(description, contents) 1141 folder.add_item(p) 1142 p.persist() 1143 self.monitor.unsuspend() 1144 self.configManager.config_altered(False)
1145
1146 - def create_abbreviation(self, folder, description, abbr, contents):
1147 """ 1148 Create a text abbreviation 1149 1150 Usage: C{engine.create_abbreviation(folder, description, abbr, contents)} 1151 1152 When the given abbreviation is typed, it will be replaced with the given 1153 text. 1154 1155 @param folder: folder to place the abbreviation in, retrieved using C{engine.get_folder()} 1156 @param description: description for the phrase 1157 @param abbr: the abbreviation that will trigger the expansion 1158 @param contents: the expansion text 1159 @raise Exception: if the specified abbreviation is not unique 1160 """ 1161 if not self.configManager.check_abbreviation_unique(abbr, None, None): 1162 raise Exception("The specified abbreviation is already in use") 1163 1164 self.monitor.suspend() 1165 p = model.Phrase(description, contents) 1166 p.modes.append(model.TriggerMode.ABBREVIATION) 1167 p.abbreviations = [abbr] 1168 folder.add_item(p) 1169 p.persist() 1170 self.monitor.unsuspend() 1171 self.configManager.config_altered(False)
1172
1173 - def create_hotkey(self, folder, description, modifiers, key, contents):
1174 """ 1175 Create a text hotkey 1176 1177 Usage: C{engine.create_hotkey(folder, description, modifiers, key, contents)} 1178 1179 When the given hotkey is pressed, it will be replaced with the given 1180 text. Modifiers must be given as a list of strings, with the following 1181 values permitted: 1182 1183 <ctrl> 1184 <alt> 1185 <super> 1186 <hyper> 1187 <shift> 1188 1189 The key must be an unshifted character (i.e. lowercase) 1190 1191 @param folder: folder to place the abbreviation in, retrieved using C{engine.get_folder()} 1192 @param description: description for the phrase 1193 @param modifiers: modifiers to use with the hotkey (as a list) 1194 @param key: the hotkey 1195 @param contents: the expansion text 1196 @raise Exception: if the specified hotkey is not unique 1197 """ 1198 modifiers.sort() 1199 if not self.configManager.check_hotkey_unique(modifiers, key, None, None): 1200 raise Exception("The specified hotkey and modifier combination is already in use") 1201 1202 self.monitor.suspend() 1203 p = model.Phrase(description, contents) 1204 p.modes.append(model.TriggerMode.HOTKEY) 1205 p.set_hotkey(modifiers, key) 1206 folder.add_item(p) 1207 p.persist() 1208 self.monitor.unsuspend() 1209 self.configManager.config_altered(False)
1210
1211 - def run_script(self, description):
1212 """ 1213 Run an existing script using its description to look it up 1214 1215 Usage: C{engine.run_script(description)} 1216 1217 @param description: description of the script to run 1218 @raise Exception: if the specified script does not exist 1219 """ 1220 targetScript = None 1221 for item in self.configManager.allItems: 1222 if item.description == description and isinstance(item, model.Script): 1223 targetScript = item 1224 1225 if targetScript is not None: 1226 self.runner.run_subscript(targetScript) 1227 else: 1228 raise Exception("No script with description '%s' found" % description)
1229
1230 - def run_script_from_macro(self, args):
1231 """ 1232 Used internally by AutoKey for phrase macros 1233 """ 1234 self.__macroArgs = args["args"].split(',') 1235 1236 try: 1237 self.run_script(args["name"]) 1238 except Exception, e: 1239 self.set_return_value("{ERROR: %s}" % str(e))
1240
1241 - def get_macro_arguments(self):
1242 """ 1243 Get the arguments supplied to the current script via its macro 1244 1245 Usage: C{engine.get_macro_arguments()} 1246 1247 @return: the arguments 1248 @rtype: C{list(str())} 1249 """ 1250 return self.__macroArgs
1251
1252 - def set_return_value(self, val):
1253 """ 1254 Store a return value to be used by a phrase macro 1255 1256 Usage: C{engine.set_return_value(val)} 1257 1258 @param val: value to be stored 1259 """ 1260 self.__returnValue = val
1261
1262 - def get_return_value(self):
1263 """ 1264 Used internally by AutoKey for phrase macros 1265 """ 1266 ret = self.__returnValue 1267 self.__returnValue = '' 1268 return ret
1269

autokey-0.90.4/doc/scripting/lib.scripting.Store-class.html0000664000175000017500000004254311744235773022720 0ustar chrischris lib.scripting.Store
Package lib :: Module scripting :: Class Store
[hide private]
[frames] | no frames]

Class Store

source code

object --+    
         |    
      dict --+
             |
            Store

Allows persistent storage of values between invocations of the script.

Instance Methods [hide private]
 
set_value(self, key, value)
Store a value
source code
 
get_value(self, key)
Get a value
source code
 
remove_value(self, key)
Remove a value
source code
 
set_global_value(self, key, value)
Store a global value
source code
 
get_global_value(self, key)
Get a global value
source code
 
remove_global_value(self, key)
Remove a global value
source code

Inherited from dict: __cmp__, __contains__, __delitem__, __eq__, __ge__, __getattribute__, __getitem__, __gt__, __init__, __iter__, __le__, __len__, __lt__, __ne__, __new__, __repr__, __setitem__, __sizeof__, clear, copy, fromkeys, get, has_key, items, iteritems, iterkeys, itervalues, keys, pop, popitem, setdefault, update, values, viewitems, viewkeys, viewvalues

Inherited from object: __delattr__, __format__, __reduce__, __reduce_ex__, __setattr__, __str__, __subclasshook__

Class Variables [hide private]

Inherited from dict: __hash__

Properties [hide private]

Inherited from object: __class__

Method Details [hide private]

set_value(self, key, value)

source code 

Store a value

Usage: store.set_value(key, value)

get_value(self, key)

source code 

Get a value

Usage: store.get_value(key)

remove_value(self, key)

source code 

Remove a value

Usage: store.remove_value(key)

set_global_value(self, key, value)

source code 

Store a global value

Usage: store.set_global_value(key, value)

The value stored with this method will be available to all scripts.

get_global_value(self, key)

source code 

Get a global value

Usage: store.get_global_value(key)

remove_global_value(self, key)

source code 

Remove a global value

Usage: store.remove_global_value(key)


autokey-0.90.4/doc/scripting/toc-everything.html0000664000175000017500000000422011724374236020704 0ustar chrischris Everything

Everything


All Classes

lib.scripting.Engine
lib.scripting.GtkClipboard
lib.scripting.GtkDialog
lib.scripting.Keyboard
lib.scripting.Mouse
lib.scripting.QtClipboard
lib.scripting.QtDialog
lib.scripting.Store
lib.scripting.System
lib.scripting.Window

All Variables

lib.scripting.__package__

[hide private] autokey-0.90.4/doc/scripting/epydoc.css0000664000175000017500000003722711361556176017063 0ustar chrischris /* Epydoc CSS Stylesheet * * This stylesheet can be used to customize the appearance of epydoc's * HTML output. * */ /* Default Colors & Styles * - Set the default foreground & background color with 'body'; and * link colors with 'a:link' and 'a:visited'. * - Use bold for decision list terms. * - The heading styles defined here are used for headings *within* * docstring descriptions. All headings used by epydoc itself use * either class='epydoc' or class='toc' (CSS styles for both * defined below). */ body { background: #ffffff; color: #000000; } p { margin-top: 0.5em; margin-bottom: 0.5em; } a:link { color: #0000ff; } a:visited { color: #204080; } dt { font-weight: bold; } h1 { font-size: +140%; font-style: italic; font-weight: bold; } h2 { font-size: +125%; font-style: italic; font-weight: bold; } h3 { font-size: +110%; font-style: italic; font-weight: normal; } code { font-size: 100%; } /* N.B.: class, not pseudoclass */ a.link { font-family: monospace; } /* Page Header & Footer * - The standard page header consists of a navigation bar (with * pointers to standard pages such as 'home' and 'trees'); a * breadcrumbs list, which can be used to navigate to containing * classes or modules; options links, to show/hide private * variables and to show/hide frames; and a page title (using *

). The page title may be followed by a link to the * corresponding source code (using 'span.codelink'). * - The footer consists of a navigation bar, a timestamp, and a * pointer to epydoc's homepage. */ h1.epydoc { margin: 0; font-size: +140%; font-weight: bold; } h2.epydoc { font-size: +130%; font-weight: bold; } h3.epydoc { font-size: +115%; font-weight: bold; margin-top: 0.2em; } td h3.epydoc { font-size: +115%; font-weight: bold; margin-bottom: 0; } table.navbar { background: #a0c0ff; color: #000000; border: 2px groove #c0d0d0; } table.navbar table { color: #000000; } th.navbar-select { background: #70b0ff; color: #000000; } table.navbar a { text-decoration: none; } table.navbar a:link { color: #0000ff; } table.navbar a:visited { color: #204080; } span.breadcrumbs { font-size: 85%; font-weight: bold; } span.options { font-size: 70%; } span.codelink { font-size: 85%; } td.footer { font-size: 85%; } /* Table Headers * - Each summary table and details section begins with a 'header' * row. This row contains a section title (marked by * 'span.table-header') as well as a show/hide private link * (marked by 'span.options', defined above). * - Summary tables that contain user-defined groups mark those * groups using 'group header' rows. */ td.table-header { background: #70b0ff; color: #000000; border: 1px solid #608090; } td.table-header table { color: #000000; } td.table-header table a:link { color: #0000ff; } td.table-header table a:visited { color: #204080; } span.table-header { font-size: 120%; font-weight: bold; } th.group-header { background: #c0e0f8; color: #000000; text-align: left; font-style: italic; font-size: 115%; border: 1px solid #608090; } /* Summary Tables (functions, variables, etc) * - Each object is described by a single row of the table with * two cells. The left cell gives the object's type, and is * marked with 'code.summary-type'. The right cell gives the * object's name and a summary description. * - CSS styles for the table's header and group headers are * defined above, under 'Table Headers' */ table.summary { border-collapse: collapse; background: #e8f0f8; color: #000000; border: 1px solid #608090; margin-bottom: 0.5em; } td.summary { border: 1px solid #608090; } code.summary-type { font-size: 85%; } table.summary a:link { color: #0000ff; } table.summary a:visited { color: #204080; } /* Details Tables (functions, variables, etc) * - Each object is described in its own div. * - A single-row summary table w/ table-header is used as * a header for each details section (CSS style for table-header * is defined above, under 'Table Headers'). */ table.details { border-collapse: collapse; background: #e8f0f8; color: #000000; border: 1px solid #608090; margin: .2em 0 0 0; } table.details table { color: #000000; } table.details a:link { color: #0000ff; } table.details a:visited { color: #204080; } /* Fields */ dl.fields { margin-left: 2em; margin-top: 1em; margin-bottom: 1em; } dl.fields dd ul { margin-left: 0em; padding-left: 0em; } dl.fields dd ul li ul { margin-left: 2em; padding-left: 0em; } div.fields { margin-left: 2em; } div.fields p { margin-bottom: 0.5em; } /* Index tables (identifier index, term index, etc) * - link-index is used for indices containing lists of links * (namely, the identifier index & term index). * - index-where is used in link indices for the text indicating * the container/source for each link. * - metadata-index is used for indices containing metadata * extracted from fields (namely, the bug index & todo index). */ table.link-index { border-collapse: collapse; background: #e8f0f8; color: #000000; border: 1px solid #608090; } td.link-index { border-width: 0px; } table.link-index a:link { color: #0000ff; } table.link-index a:visited { color: #204080; } span.index-where { font-size: 70%; } table.metadata-index { border-collapse: collapse; background: #e8f0f8; color: #000000; border: 1px solid #608090; margin: .2em 0 0 0; } td.metadata-index { border-width: 1px; border-style: solid; } table.metadata-index a:link { color: #0000ff; } table.metadata-index a:visited { color: #204080; } /* Function signatures * - sig* is used for the signature in the details section. * - .summary-sig* is used for the signature in the summary * table, and when listing property accessor functions. * */ .sig-name { color: #006080; } .sig-arg { color: #008060; } .sig-default { color: #602000; } .summary-sig { font-family: monospace; } .summary-sig-name { color: #006080; font-weight: bold; } table.summary a.summary-sig-name:link { color: #006080; font-weight: bold; } table.summary a.summary-sig-name:visited { color: #006080; font-weight: bold; } .summary-sig-arg { color: #006040; } .summary-sig-default { color: #501800; } /* Subclass list */ ul.subclass-list { display: inline; } ul.subclass-list li { display: inline; } /* To render variables, classes etc. like functions */ table.summary .summary-name { color: #006080; font-weight: bold; font-family: monospace; } table.summary a.summary-name:link { color: #006080; font-weight: bold; font-family: monospace; } table.summary a.summary-name:visited { color: #006080; font-weight: bold; font-family: monospace; } /* Variable values * - In the 'variable details' sections, each varaible's value is * listed in a 'pre.variable' box. The width of this box is * restricted to 80 chars; if the value's repr is longer than * this it will be wrapped, using a backslash marked with * class 'variable-linewrap'. If the value's repr is longer * than 3 lines, the rest will be ellided; and an ellipsis * marker ('...' marked with 'variable-ellipsis') will be used. * - If the value is a string, its quote marks will be marked * with 'variable-quote'. * - If the variable is a regexp, it is syntax-highlighted using * the re* CSS classes. */ pre.variable { padding: .5em; margin: 0; background: #dce4ec; color: #000000; border: 1px solid #708890; } .variable-linewrap { color: #604000; font-weight: bold; } .variable-ellipsis { color: #604000; font-weight: bold; } .variable-quote { color: #604000; font-weight: bold; } .variable-group { color: #008000; font-weight: bold; } .variable-op { color: #604000; font-weight: bold; } .variable-string { color: #006030; } .variable-unknown { color: #a00000; font-weight: bold; } .re { color: #000000; } .re-char { color: #006030; } .re-op { color: #600000; } .re-group { color: #003060; } .re-ref { color: #404040; } /* Base tree * - Used by class pages to display the base class hierarchy. */ pre.base-tree { font-size: 80%; margin: 0; } /* Frames-based table of contents headers * - Consists of two frames: one for selecting modules; and * the other listing the contents of the selected module. * - h1.toc is used for each frame's heading * - h2.toc is used for subheadings within each frame. */ h1.toc { text-align: center; font-size: 105%; margin: 0; font-weight: bold; padding: 0; } h2.toc { font-size: 100%; font-weight: bold; margin: 0.5em 0 0 -0.3em; } /* Syntax Highlighting for Source Code * - doctest examples are displayed in a 'pre.py-doctest' block. * If the example is in a details table entry, then it will use * the colors specified by the 'table pre.py-doctest' line. * - Source code listings are displayed in a 'pre.py-src' block. * Each line is marked with 'span.py-line' (used to draw a line * down the left margin, separating the code from the line * numbers). Line numbers are displayed with 'span.py-lineno'. * The expand/collapse block toggle button is displayed with * 'a.py-toggle' (Note: the CSS style for 'a.py-toggle' should not * modify the font size of the text.) * - If a source code page is opened with an anchor, then the * corresponding code block will be highlighted. The code * block's header is highlighted with 'py-highlight-hdr'; and * the code block's body is highlighted with 'py-highlight'. * - The remaining py-* classes are used to perform syntax * highlighting (py-string for string literals, py-name for names, * etc.) */ pre.py-doctest { padding: .5em; margin: 1em; background: #e8f0f8; color: #000000; border: 1px solid #708890; } table pre.py-doctest { background: #dce4ec; color: #000000; } pre.py-src { border: 2px solid #000000; background: #f0f0f0; color: #000000; } .py-line { border-left: 2px solid #000000; margin-left: .2em; padding-left: .4em; } .py-lineno { font-style: italic; font-size: 90%; padding-left: .5em; } a.py-toggle { text-decoration: none; } div.py-highlight-hdr { border-top: 2px solid #000000; border-bottom: 2px solid #000000; background: #d8e8e8; } div.py-highlight { border-bottom: 2px solid #000000; background: #d0e0e0; } .py-prompt { color: #005050; font-weight: bold;} .py-more { color: #005050; font-weight: bold;} .py-string { color: #006030; } .py-comment { color: #003060; } .py-keyword { color: #600000; } .py-output { color: #404040; } .py-name { color: #000050; } .py-name:link { color: #000050 !important; } .py-name:visited { color: #000050 !important; } .py-number { color: #005000; } .py-defname { color: #000060; font-weight: bold; } .py-def-name { color: #000060; font-weight: bold; } .py-base-class { color: #000060; } .py-param { color: #000060; } .py-docstring { color: #006030; } .py-decorator { color: #804020; } /* Use this if you don't want links to names underlined: */ /*a.py-name { text-decoration: none; }*/ /* Graphs & Diagrams * - These CSS styles are used for graphs & diagrams generated using * Graphviz dot. 'img.graph-without-title' is used for bare * diagrams (to remove the border created by making the image * clickable). */ img.graph-without-title { border: none; } img.graph-with-title { border: 1px solid #000000; } span.graph-title { font-weight: bold; } span.graph-caption { } /* General-purpose classes * - 'p.indent-wrapped-lines' defines a paragraph whose first line * is not indented, but whose subsequent lines are. * - The 'nomargin-top' class is used to remove the top margin (e.g. * from lists). The 'nomargin' class is used to remove both the * top and bottom margin (but not the left or right margin -- * for lists, that would cause the bullets to disappear.) */ p.indent-wrapped-lines { padding: 0 0 0 7em; text-indent: -7em; margin: 0; } .nomargin-top { margin-top: 0; } .nomargin { margin-top: 0; margin-bottom: 0; } /* HTML Log */ div.log-block { padding: 0; margin: .5em 0 .5em 0; background: #e8f0f8; color: #000000; border: 1px solid #000000; } div.log-error { padding: .1em .3em .1em .3em; margin: 4px; background: #ffb0b0; color: #000000; border: 1px solid #000000; } div.log-warning { padding: .1em .3em .1em .3em; margin: 4px; background: #ffffb0; color: #000000; border: 1px solid #000000; } div.log-info { padding: .1em .3em .1em .3em; margin: 4px; background: #b0ffb0; color: #000000; border: 1px solid #000000; } h2.log-hdr { background: #70b0ff; color: #000000; margin: 0; padding: 0em 0.5em 0em 0.5em; border-bottom: 1px solid #000000; font-size: 110%; } p.log { font-weight: bold; margin: .5em 0 .5em 0; } tr.opt-changed { color: #000000; font-weight: bold; } tr.opt-default { color: #606060; } pre.log { margin: 0; padding: 0; padding-left: 1em; } autokey-0.90.4/doc/scripting/index.html0000664000175000017500000000112311361556017017037 0ustar chrischris API Documentation autokey-0.90.4/doc/scripting/lib.scripting.Keyboard-class.html0000664000175000017500000004300011744235773023351 0ustar chrischris lib.scripting.Keyboard
Package lib :: Module scripting :: Class Keyboard
[hide private]
[frames] | no frames]

Class Keyboard

source code

Provides access to the keyboard for event generation.

Instance Methods [hide private]
 
__init__(self, mediator) source code
 
send_keys(self, keyString)
Send a sequence of keys via keyboard events
source code
 
send_key(self, key, repeat=1)
Send a keyboard event
source code
 
press_key(self, key)
Send a key down event
source code
 
release_key(self, key)
Send a key up event
source code
 
fake_keypress(self, key, repeat=1)
Fake a keypress
source code
 
wait_for_keypress(self, key, modifiers=[], timeOut=10.0)
Wait for a keypress or key combination
source code
Method Details [hide private]

send_keys(self, keyString)

source code 

Send a sequence of keys via keyboard events

Usage: keyboard.send_keys(keyString)

Parameters:
  • keyString - string of keys (including special keys) to send

send_key(self, key, repeat=1)

source code 

Send a keyboard event

Usage: keyboard.send_key(key, repeat=1)

Parameters:
  • key - they key to be sent (e.g. "s" or "<enter>")
  • repeat - number of times to repeat the key event

press_key(self, key)

source code 

Send a key down event

Usage: keyboard.press_key(key)

The key will be treated as down until a matching release_key() is sent.

Parameters:
  • key - they key to be pressed (e.g. "s" or "<enter>")

release_key(self, key)

source code 

Send a key up event

Usage: keyboard.release_key(key)

If the specified key was not made down using press_key(), the event will be ignored.

Parameters:
  • key - they key to be released (e.g. "s" or "<enter>")

fake_keypress(self, key, repeat=1)

source code 

Fake a keypress

Usage: keyboard.fake_keypress(key, repeat=1)

Uses XTest to 'fake' a keypress. This is useful to send keypresses to some applications which won't respond to keyboard.send_key()

Parameters:
  • key - they key to be sent (e.g. "s" or "<enter>")
  • repeat - number of times to repeat the key event

wait_for_keypress(self, key, modifiers=[], timeOut=10.0)

source code 

Wait for a keypress or key combination

Usage: keyboard.wait_for_keypress(self, key, modifiers=[], timeOut=10.0)

Note: this function cannot be used to wait for modifier keys on their own

Parameters:
  • key - they key to wait for
  • modifiers - list of modifiers that should be pressed with the key
  • timeOut - maximum time, in seconds, to wait for the keypress to occur

autokey-0.90.4/doc/scripting/api-objects.txt0000664000175000017500000001704211744235773020022 0ustar chrischrislib.scripting lib.scripting-module.html lib.scripting.__package__ lib.scripting-module.html#__package__ lib.scripting.Engine lib.scripting.Engine-class.html lib.scripting.Engine.run_script lib.scripting.Engine-class.html#run_script lib.scripting.Engine.get_folder lib.scripting.Engine-class.html#get_folder lib.scripting.Engine.get_return_value lib.scripting.Engine-class.html#get_return_value lib.scripting.Engine.create_hotkey lib.scripting.Engine-class.html#create_hotkey lib.scripting.Engine.run_script_from_macro lib.scripting.Engine-class.html#run_script_from_macro lib.scripting.Engine.set_return_value lib.scripting.Engine-class.html#set_return_value lib.scripting.Engine.create_abbreviation lib.scripting.Engine-class.html#create_abbreviation lib.scripting.Engine.create_phrase lib.scripting.Engine-class.html#create_phrase lib.scripting.Engine.__init__ lib.scripting.Engine-class.html#__init__ lib.scripting.Engine.get_macro_arguments lib.scripting.Engine-class.html#get_macro_arguments lib.scripting.GtkClipboard lib.scripting.GtkClipboard-class.html lib.scripting.GtkClipboard.get_selection lib.scripting.GtkClipboard-class.html#get_selection lib.scripting.GtkClipboard.__receive lib.scripting.GtkClipboard-class.html#__receive lib.scripting.GtkClipboard.get_clipboard lib.scripting.GtkClipboard-class.html#get_clipboard lib.scripting.GtkClipboard.__fillClipboard lib.scripting.GtkClipboard-class.html#__fillClipboard lib.scripting.GtkClipboard.fill_clipboard lib.scripting.GtkClipboard-class.html#fill_clipboard lib.scripting.GtkClipboard.fill_selection lib.scripting.GtkClipboard-class.html#fill_selection lib.scripting.GtkClipboard.__execAsync lib.scripting.GtkClipboard-class.html#__execAsync lib.scripting.GtkClipboard.__fillSelection lib.scripting.GtkClipboard-class.html#__fillSelection lib.scripting.GtkClipboard.__init__ lib.scripting.GtkClipboard-class.html#__init__ lib.scripting.GtkDialog lib.scripting.GtkDialog-class.html lib.scripting.GtkDialog.input_dialog lib.scripting.GtkDialog-class.html#input_dialog lib.scripting.GtkDialog.info_dialog lib.scripting.GtkDialog-class.html#info_dialog lib.scripting.GtkDialog.list_menu lib.scripting.GtkDialog-class.html#list_menu lib.scripting.GtkDialog.open_file lib.scripting.GtkDialog-class.html#open_file lib.scripting.GtkDialog.password_dialog lib.scripting.GtkDialog-class.html#password_dialog lib.scripting.GtkDialog.list_menu_multi lib.scripting.GtkDialog-class.html#list_menu_multi lib.scripting.GtkDialog.__runZenity lib.scripting.GtkDialog-class.html#__runZenity lib.scripting.GtkDialog.save_file lib.scripting.GtkDialog-class.html#save_file lib.scripting.GtkDialog.calendar lib.scripting.GtkDialog-class.html#calendar lib.scripting.GtkDialog.choose_directory lib.scripting.GtkDialog-class.html#choose_directory lib.scripting.Keyboard lib.scripting.Keyboard-class.html lib.scripting.Keyboard.press_key lib.scripting.Keyboard-class.html#press_key lib.scripting.Keyboard.fake_keypress lib.scripting.Keyboard-class.html#fake_keypress lib.scripting.Keyboard.send_key lib.scripting.Keyboard-class.html#send_key lib.scripting.Keyboard.send_keys lib.scripting.Keyboard-class.html#send_keys lib.scripting.Keyboard.release_key lib.scripting.Keyboard-class.html#release_key lib.scripting.Keyboard.wait_for_keypress lib.scripting.Keyboard-class.html#wait_for_keypress lib.scripting.Keyboard.__init__ lib.scripting.Keyboard-class.html#__init__ lib.scripting.Mouse lib.scripting.Mouse-class.html lib.scripting.Mouse.wait_for_click lib.scripting.Mouse-class.html#wait_for_click lib.scripting.Mouse.click_relative lib.scripting.Mouse-class.html#click_relative lib.scripting.Mouse.click_absolute lib.scripting.Mouse-class.html#click_absolute lib.scripting.Mouse.click_relative_self lib.scripting.Mouse-class.html#click_relative_self lib.scripting.Mouse.__init__ lib.scripting.Mouse-class.html#__init__ lib.scripting.QtClipboard lib.scripting.QtClipboard-class.html lib.scripting.QtClipboard.get_selection lib.scripting.QtClipboard-class.html#get_selection lib.scripting.QtClipboard.get_clipboard lib.scripting.QtClipboard-class.html#get_clipboard lib.scripting.QtClipboard.fill_clipboard lib.scripting.QtClipboard-class.html#fill_clipboard lib.scripting.QtClipboard.__fillClipboard lib.scripting.QtClipboard-class.html#__fillClipboard lib.scripting.QtClipboard.__getSelection lib.scripting.QtClipboard-class.html#__getSelection lib.scripting.QtClipboard.__getClipboard lib.scripting.QtClipboard-class.html#__getClipboard lib.scripting.QtClipboard.fill_selection lib.scripting.QtClipboard-class.html#fill_selection lib.scripting.QtClipboard.__execAsync lib.scripting.QtClipboard-class.html#__execAsync lib.scripting.QtClipboard.__fillSelection lib.scripting.QtClipboard-class.html#__fillSelection lib.scripting.QtClipboard.__init__ lib.scripting.QtClipboard-class.html#__init__ lib.scripting.QtDialog lib.scripting.QtDialog-class.html lib.scripting.QtDialog.input_dialog lib.scripting.QtDialog-class.html#input_dialog lib.scripting.QtDialog.info_dialog lib.scripting.QtDialog-class.html#info_dialog lib.scripting.QtDialog.list_menu lib.scripting.QtDialog-class.html#list_menu lib.scripting.QtDialog.open_file lib.scripting.QtDialog-class.html#open_file lib.scripting.QtDialog.password_dialog lib.scripting.QtDialog-class.html#password_dialog lib.scripting.QtDialog.list_menu_multi lib.scripting.QtDialog-class.html#list_menu_multi lib.scripting.QtDialog.combo_menu lib.scripting.QtDialog-class.html#combo_menu lib.scripting.QtDialog.save_file lib.scripting.QtDialog-class.html#save_file lib.scripting.QtDialog.__runKdialog lib.scripting.QtDialog-class.html#__runKdialog lib.scripting.QtDialog.calendar lib.scripting.QtDialog-class.html#calendar lib.scripting.QtDialog.choose_directory lib.scripting.QtDialog-class.html#choose_directory lib.scripting.QtDialog.choose_colour lib.scripting.QtDialog-class.html#choose_colour lib.scripting.Store lib.scripting.Store-class.html lib.scripting.Store.set_global_value lib.scripting.Store-class.html#set_global_value lib.scripting.Store.get_value lib.scripting.Store-class.html#get_value lib.scripting.Store.set_value lib.scripting.Store-class.html#set_value lib.scripting.Store.get_global_value lib.scripting.Store-class.html#get_global_value lib.scripting.Store.remove_value lib.scripting.Store-class.html#remove_value lib.scripting.Store.remove_global_value lib.scripting.Store-class.html#remove_global_value lib.scripting.System lib.scripting.System-class.html lib.scripting.System.exec_command lib.scripting.System-class.html#exec_command lib.scripting.System.create_file lib.scripting.System-class.html#create_file lib.scripting.Window lib.scripting.Window-class.html lib.scripting.Window.wait_for_exist lib.scripting.Window-class.html#wait_for_exist lib.scripting.Window.activate lib.scripting.Window-class.html#activate lib.scripting.Window.move_to_desktop lib.scripting.Window-class.html#move_to_desktop lib.scripting.Window.get_active_geometry lib.scripting.Window-class.html#get_active_geometry lib.scripting.Window.get_active_class lib.scripting.Window-class.html#get_active_class lib.scripting.Window.wait_for_focus lib.scripting.Window-class.html#wait_for_focus lib.scripting.Window.__runWmctrl lib.scripting.Window-class.html#__runWmctrl lib.scripting.Window.__init__ lib.scripting.Window-class.html#__init__ lib.scripting.Window.resize_move lib.scripting.Window-class.html#resize_move lib.scripting.Window.close lib.scripting.Window-class.html#close lib.scripting.Window.switch_desktop lib.scripting.Window-class.html#switch_desktop lib.scripting.Window.set_property lib.scripting.Window-class.html#set_property lib.scripting.Window.get_active_title lib.scripting.Window-class.html#get_active_title autokey-0.90.4/doc/scripting/toc-lib.scripting-module.html0000664000175000017500000000376311724374236022565 0ustar chrischris scripting

Module scripting


Classes

Engine
GtkClipboard
GtkDialog
Keyboard
Mouse
QtClipboard
QtDialog
Store
System
Window

Variables

__package__

[hide private] autokey-0.90.4/doc/scripting/frames.html0000664000175000017500000000112311361556017017205 0ustar chrischris API Documentation autokey-0.90.4/doc/scripting/lib.scripting.QtClipboard-class.html0000664000175000017500000003715111744235773024027 0ustar chrischris lib.scripting.QtClipboard
Package lib :: Module scripting :: Class QtClipboard
[hide private]
[frames] | no frames]

Class QtClipboard

source code

Read/write access to the X selection and clipboard - QT version

Instance Methods [hide private]
 
__init__(self, app) source code
 
fill_selection(self, contents)
Copy text into the X selection
source code
 
__fillSelection(self, string) source code
str
get_selection(self)
Read text from the X selection
source code
 
__getSelection(self) source code
 
fill_clipboard(self, contents)
Copy text into the clipboard
source code
 
__fillClipboard(self, string) source code
str
get_clipboard(self)
Read text from the clipboard
source code
 
__getClipboard(self) source code
 
__execAsync(self, callback, *args) source code
Method Details [hide private]

fill_selection(self, contents)

source code 

Copy text into the X selection

Usage: clipboard.fill_selection(contents)

Parameters:
  • contents - string to be placed in the selection

get_selection(self)

source code 

Read text from the X selection

Usage: clipboard.get_selection()

Returns: str
text contents of the mouse selection

fill_clipboard(self, contents)

source code 

Copy text into the clipboard

Usage: clipboard.fill_clipboard(contents)

Parameters:
  • contents - string to be placed in the selection

get_clipboard(self)

source code 

Read text from the clipboard

Usage: clipboard.get_clipboard()

Returns: str
text contents of the clipboard

autokey-0.90.4/doc/scripting/lib.scripting.QtDialog-class.html0000664000175000017500000012202211744235773023317 0ustar chrischris lib.scripting.QtDialog
Package lib :: Module scripting :: Class QtDialog
[hide private]
[frames] | no frames]

Class QtDialog

source code

Provides a simple interface for the display of some basic dialogs to collect information from the user.

This version uses KDialog to integrate well with KDE. To pass additional arguments to KDialog that are not specifically handled, use keyword arguments. For example, to pass the --geometry argument to KDialog to specify the desired size of the dialog, pass geometry="700x400" as one of the parameters. All keyword arguments must be given as strings.

A note on exit codes: an exit code of 0 indicates that the user clicked OK.

Instance Methods [hide private]
 
__runKdialog(self, title, args, kwargs) source code
tuple(int, str)
info_dialog(self, title='Information', message='', **kwargs)
Show an information dialog
source code
tuple(int, str)
input_dialog(self, title='Enter a value', message='Enter a value', default='', **kwargs)
Show an input dialog
source code
tuple(int, str)
password_dialog(self, title='Enter password', message='Enter password', **kwargs)
Show a password input dialog
source code
tuple(int, str)
combo_menu(self, options, title='Choose an option', message='Choose an option', **kwargs)
Show a combobox menu
source code
tuple(int, str)
list_menu(self, options, title='Choose a value', message='Choose a value', default=None, **kwargs)
Show a single-selection list menu
source code
tuple(int, str)
list_menu_multi(self, options, title='Choose one or more values', message='Choose one or more values', defaults=[], **kwargs)
Show a multiple-selection list menu
source code
tuple(int, str)
open_file(self, title='Open File', initialDir='~', fileTypes='*|All Files', rememberAs=None, **kwargs)
Show an Open File dialog
source code
tuple(int, str)
save_file(self, title='Save As', initialDir='~', fileTypes='*|All Files', rememberAs=None, **kwargs)
Show a Save As dialog
source code
tuple(int, str)
choose_directory(self, title='Select Directory', initialDir='~', rememberAs=None, **kwargs)
Show a Directory Chooser dialog
source code
tuple(int, str)
choose_colour(self, title='Select Colour', **kwargs)
Show a Colour Chooser dialog
source code
tuple(int, str)
calendar(self, title='Choose a date', format='%Y-%m-%d', date='today', **kwargs)
Show a calendar dialog
source code
Method Details [hide private]

info_dialog(self, title='Information', message='', **kwargs)

source code 

Show an information dialog

Usage: dialog.info_dialog(title="Information", message="", **kwargs)

Parameters:
  • title - window title for the dialog
  • message - message displayed in the dialog
Returns: tuple(int, str)
a tuple containing the exit code and user input

input_dialog(self, title='Enter a value', message='Enter a value', default='', **kwargs)

source code 

Show an input dialog

Usage: dialog.input_dialog(title="Enter a value", message="Enter a value", default="", **kwargs)

Parameters:
  • title - window title for the dialog
  • message - message displayed above the input box
  • default - default value for the input box
Returns: tuple(int, str)
a tuple containing the exit code and user input

password_dialog(self, title='Enter password', message='Enter password', **kwargs)

source code 

Show a password input dialog

Usage: dialog.password_dialog(title="Enter password", message="Enter password", **kwargs)

Parameters:
  • title - window title for the dialog
  • message - message displayed above the password input box
Returns: tuple(int, str)
a tuple containing the exit code and user input

combo_menu(self, options, title='Choose an option', message='Choose an option', **kwargs)

source code 

Show a combobox menu

Usage: dialog.combo_menu(options, title="Choose an option", message="Choose an option", **kwargs)

Parameters:
  • options - list of options (strings) for the dialog
  • title - window title for the dialog
  • message - message displayed above the combobox
Returns: tuple(int, str)
a tuple containing the exit code and user choice

list_menu(self, options, title='Choose a value', message='Choose a value', default=None, **kwargs)

source code 

Show a single-selection list menu

Usage: dialog.list_menu(options, title="Choose a value", message="Choose a value", default=None, **kwargs)

Parameters:
  • options - list of options (strings) for the dialog
  • title - window title for the dialog
  • message - message displayed above the list
  • default - default value to be selected
Returns: tuple(int, str)
a tuple containing the exit code and user choice

list_menu_multi(self, options, title='Choose one or more values', message='Choose one or more values', defaults=[], **kwargs)

source code 

Show a multiple-selection list menu

Usage: dialog.list_menu_multi(options, title="Choose one or more values", message="Choose one or more values", defaults=[], **kwargs)

Parameters:
  • options - list of options (strings) for the dialog
  • title - window title for the dialog
  • message - message displayed above the list
  • defaults - list of default values to be selected
Returns: tuple(int, str)
a tuple containing the exit code and user choice

open_file(self, title='Open File', initialDir='~', fileTypes='*|All Files', rememberAs=None, **kwargs)

source code 

Show an Open File dialog

Usage: dialog.open_file(title="Open File", initialDir="~", fileTypes="*|All Files", rememberAs=None, **kwargs)

Parameters:
  • title - window title for the dialog
  • initialDir - starting directory for the file dialog
  • fileTypes - file type filter expression
  • rememberAs - gives an ID to this file dialog, allowing it to open at the last used path next time
Returns: tuple(int, str)
a tuple containing the exit code and file path

save_file(self, title='Save As', initialDir='~', fileTypes='*|All Files', rememberAs=None, **kwargs)

source code 

Show a Save As dialog

Usage: dialog.save_file(title="Save As", initialDir="~", fileTypes="*|All Files", rememberAs=None, **kwargs)

Parameters:
  • title - window title for the dialog
  • initialDir - starting directory for the file dialog
  • fileTypes - file type filter expression
  • rememberAs - gives an ID to this file dialog, allowing it to open at the last used path next time
Returns: tuple(int, str)
a tuple containing the exit code and file path

choose_directory(self, title='Select Directory', initialDir='~', rememberAs=None, **kwargs)

source code 

Show a Directory Chooser dialog

Usage: dialog.choose_directory(title="Select Directory", initialDir="~", rememberAs=None, **kwargs)

Parameters:
  • title - window title for the dialog
  • initialDir - starting directory for the directory chooser dialog
  • rememberAs - gives an ID to this file dialog, allowing it to open at the last used path next time
Returns: tuple(int, str)
a tuple containing the exit code and chosen path

choose_colour(self, title='Select Colour', **kwargs)

source code 

Show a Colour Chooser dialog

Usage: dialog.choose_colour(title="Select Colour")

Parameters:
  • title - window title for the dialog
Returns: tuple(int, str)
a tuple containing the exit code and colour

calendar(self, title='Choose a date', format='%Y-%m-%d', date='today', **kwargs)

source code 

Show a calendar dialog

Usage: dialog.calendar_dialog(title="Choose a date", format="%Y-%m-%d", date="YYYY-MM-DD", **kwargs)

Note: the format and date parameters are not currently used

Parameters:
  • title - window title for the dialog
  • format - format of date to be returned
  • date - initial date as YYYY-MM-DD, otherwise today
Returns: tuple(int, str)
a tuple containing the exit code and date

autokey-0.90.4/doc/scripting/lib.scripting.Engine-class.html0000664000175000017500000005371411744235773023033 0ustar chrischris lib.scripting.Engine
Package lib :: Module scripting :: Class Engine
[hide private]
[frames] | no frames]

Class Engine

source code

Provides access to the internals of AutoKey.

Note that any configuration changes made using this API while the configuration window is open will not appear until it is closed and re-opened.

Instance Methods [hide private]
 
__init__(self, configManager, runner) source code
 
get_folder(self, title)
Retrieve a folder by its title
source code
 
create_phrase(self, folder, description, contents)
Create a text phrase
source code
 
create_abbreviation(self, folder, description, abbr, contents)
Create a text abbreviation
source code
 
create_hotkey(self, folder, description, modifiers, key, contents)
Create a text hotkey
source code
 
run_script(self, description)
Run an existing script using its description to look it up
source code
 
run_script_from_macro(self, args)
Used internally by AutoKey for phrase macros
source code
list(str())
get_macro_arguments(self)
Get the arguments supplied to the current script via its macro
source code
 
set_return_value(self, val)
Store a return value to be used by a phrase macro
source code
 
get_return_value(self)
Used internally by AutoKey for phrase macros
source code
Method Details [hide private]

get_folder(self, title)

source code 

Retrieve a folder by its title

Usage: engine.get_folder(title)

Note that if more than one folder has the same title, only the first match will be returned.

create_phrase(self, folder, description, contents)

source code 

Create a text phrase

Usage: engine.create_phrase(folder, description, contents)

A new phrase with no abbreviation or hotkey is created in the specified folder

Parameters:
  • folder - folder to place the abbreviation in, retrieved using engine.get_folder()
  • description - description for the phrase
  • contents - the expansion text

create_abbreviation(self, folder, description, abbr, contents)

source code 

Create a text abbreviation

Usage: engine.create_abbreviation(folder, description, abbr, contents)

When the given abbreviation is typed, it will be replaced with the given text.

Parameters:
  • folder - folder to place the abbreviation in, retrieved using engine.get_folder()
  • description - description for the phrase
  • abbr - the abbreviation that will trigger the expansion
  • contents - the expansion text
Raises:
  • Exception - if the specified abbreviation is not unique

create_hotkey(self, folder, description, modifiers, key, contents)

source code 

Create a text hotkey

Usage: engine.create_hotkey(folder, description, modifiers, key, contents)

When the given hotkey is pressed, it will be replaced with the given text. Modifiers must be given as a list of strings, with the following values permitted:

<ctrl> <alt> <super> <hyper> <shift>

The key must be an unshifted character (i.e. lowercase)

Parameters:
  • folder - folder to place the abbreviation in, retrieved using engine.get_folder()
  • description - description for the phrase
  • modifiers - modifiers to use with the hotkey (as a list)
  • key - the hotkey
  • contents - the expansion text
Raises:
  • Exception - if the specified hotkey is not unique

run_script(self, description)

source code 

Run an existing script using its description to look it up

Usage: engine.run_script(description)

Parameters:
  • description - description of the script to run
Raises:
  • Exception - if the specified script does not exist

get_macro_arguments(self)

source code 

Get the arguments supplied to the current script via its macro

Usage: engine.get_macro_arguments()

Returns: list(str())
the arguments

set_return_value(self, val)

source code 

Store a return value to be used by a phrase macro

Usage: engine.set_return_value(val)

Parameters:
  • val - value to be stored

autokey-0.90.4/doc/scripting/lib.scripting.GtkDialog-class.html0000664000175000017500000010043611744235773023465 0ustar chrischris lib.scripting.GtkDialog
Package lib :: Module scripting :: Class GtkDialog
[hide private]
[frames] | no frames]

Class GtkDialog

source code

Provides a simple interface for the display of some basic dialogs to collect information from the user.

This version uses Zenity to integrate well with GNOME. To pass additional arguments to Zenity that are not specifically handled, use keyword arguments. For example, to pass the --timeout argument to Zenity pass timeout="15" as one of the parameters. All keyword arguments must be given as strings.

A note on exit codes: an exit code of 0 indicates that the user clicked OK.

Instance Methods [hide private]
 
__runZenity(self, title, args, kwargs) source code
tuple(int, str)
info_dialog(self, title='Information', message='', **kwargs)
Show an information dialog
source code
tuple(int, str)
input_dialog(self, title='Enter a value', message='Enter a value', default='', **kwargs)
Show an input dialog
source code
tuple(int, str)
password_dialog(self, title='Enter password', message='Enter password', **kwargs)
Show a password input dialog
source code
tuple(int, str)
list_menu(self, options, title='Choose a value', message='Choose a value', default=None, **kwargs)
Show a single-selection list menu
source code
tuple(int, str)
list_menu_multi(self, options, title='Choose one or more values', message='Choose one or more values', defaults=[], **kwargs)
Show a multiple-selection list menu
source code
tuple(int, str)
open_file(self, title='Open File', **kwargs)
Show an Open File dialog
source code
tuple(int, str)
save_file(self, title='Save As', **kwargs)
Show a Save As dialog
source code
tuple(int, str)
choose_directory(self, title='Select Directory', initialDir='~', **kwargs)
Show a Directory Chooser dialog
source code
tuple(int, str)
calendar(self, title='Choose a date', format='%Y-%m-%d', date='today', **kwargs)
Show a calendar dialog
source code
Method Details [hide private]

info_dialog(self, title='Information', message='', **kwargs)

source code 

Show an information dialog

Usage: dialog.info_dialog(title="Information", message="", **kwargs)

Parameters:
  • title - window title for the dialog
  • message - message displayed in the dialog
Returns: tuple(int, str)
a tuple containing the exit code and user input

input_dialog(self, title='Enter a value', message='Enter a value', default='', **kwargs)

source code 

Show an input dialog

Usage: dialog.input_dialog(title="Enter a value", message="Enter a value", default="", **kwargs)

Parameters:
  • title - window title for the dialog
  • message - message displayed above the input box
  • default - default value for the input box
Returns: tuple(int, str)
a tuple containing the exit code and user input

password_dialog(self, title='Enter password', message='Enter password', **kwargs)

source code 

Show a password input dialog

Usage: dialog.password_dialog(title="Enter password", message="Enter password")

Parameters:
  • title - window title for the dialog
  • message - message displayed above the password input box
Returns: tuple(int, str)
a tuple containing the exit code and user input

list_menu(self, options, title='Choose a value', message='Choose a value', default=None, **kwargs)

source code 

Show a single-selection list menu

Usage: dialog.list_menu(options, title="Choose a value", message="Choose a value", default=None, **kwargs)

Parameters:
  • options - list of options (strings) for the dialog
  • title - window title for the dialog
  • message - message displayed above the list
  • default - default value to be selected
Returns: tuple(int, str)
a tuple containing the exit code and user choice

list_menu_multi(self, options, title='Choose one or more values', message='Choose one or more values', defaults=[], **kwargs)

source code 

Show a multiple-selection list menu

Usage: dialog.list_menu_multi(options, title="Choose one or more values", message="Choose one or more values", defaults=[], **kwargs)

Parameters:
  • options - list of options (strings) for the dialog
  • title - window title for the dialog
  • message - message displayed above the list
  • defaults - list of default values to be selected
Returns: tuple(int, str)
a tuple containing the exit code and user choice

open_file(self, title='Open File', **kwargs)

source code 

Show an Open File dialog

Usage: dialog.open_file(title="Open File", **kwargs)

Parameters:
  • title - window title for the dialog
Returns: tuple(int, str)
a tuple containing the exit code and file path

save_file(self, title='Save As', **kwargs)

source code 

Show a Save As dialog

Usage: dialog.save_file(title="Save As", **kwargs)

Parameters:
  • title - window title for the dialog
Returns: tuple(int, str)
a tuple containing the exit code and file path

choose_directory(self, title='Select Directory', initialDir='~', **kwargs)

source code 

Show a Directory Chooser dialog

Usage: dialog.choose_directory(title="Select Directory", **kwargs)

Parameters:
  • title - window title for the dialog
Returns: tuple(int, str)
a tuple containing the exit code and path

calendar(self, title='Choose a date', format='%Y-%m-%d', date='today', **kwargs)

source code 

Show a calendar dialog

Usage: dialog.calendar_dialog(title="Choose a date", format="%Y-%m-%d", date="YYYY-MM-DD", **kwargs)

Parameters:
  • title - window title for the dialog
  • format - format of date to be returned
  • date - initial date as YYYY-MM-DD, otherwise today
Returns: tuple(int, str)
a tuple containing the exit code and date

autokey-0.90.4/doc/scripting/lib.scripting.GtkClipboard-class.html0000664000175000017500000003700211744235773024163 0ustar chrischris lib.scripting.GtkClipboard
Package lib :: Module scripting :: Class GtkClipboard
[hide private]
[frames] | no frames]

Class GtkClipboard

source code

Read/write access to the X selection and clipboard - GTK version

Instance Methods [hide private]
 
__init__(self, app) source code
 
fill_selection(self, contents)
Copy text into the X selection
source code
 
__fillSelection(self, string) source code
str
get_selection(self)
Read text from the X selection
source code
 
__receive(self, cb, text, data=None) source code
 
fill_clipboard(self, contents)
Copy text into the clipboard
source code
 
__fillClipboard(self, string) source code
str
get_clipboard(self)
Read text from the clipboard
source code
 
__execAsync(self, callback, *args) source code
Method Details [hide private]

fill_selection(self, contents)

source code 

Copy text into the X selection

Usage: clipboard.fill_selection(contents)

Parameters:
  • contents - string to be placed in the selection

get_selection(self)

source code 

Read text from the X selection

Usage: clipboard.get_selection()

Returns: str
text contents of the mouse selection
Raises:
  • Exception - if no text was found in the selection

fill_clipboard(self, contents)

source code 

Copy text into the clipboard

Usage: clipboard.fill_clipboard(contents)

Parameters:
  • contents - string to be placed in the selection

get_clipboard(self)

source code 

Read text from the clipboard

Usage: clipboard.get_clipboard()

Returns: str
text contents of the clipboard
Raises:
  • Exception - if no text was found on the clipboard

autokey-0.90.4/doc/scripting/lib.scripting.Window-class.html0000664000175000017500000007447111744235773023100 0ustar chrischris lib.scripting.Window
Package lib :: Module scripting :: Class Window
[hide private]
[frames] | no frames]

Class Window

source code

Basic window management using wmctrl

Note: in all cases where a window title is required (with the exception of wait_for_focus()), two special values of window title are permitted:

:ACTIVE: - select the currently active window :SELECT: - select the desired window by clicking on it

Instance Methods [hide private]
 
__init__(self, mediator) source code
boolean
wait_for_focus(self, title, timeOut=5)
Wait for window with the given title to have focus
source code
boolean
wait_for_exist(self, title, timeOut=5)
Wait for window with the given title to be created
source code
 
activate(self, title, switchDesktop=False, matchClass=False)
Activate the specified window, giving it input focus
source code
 
close(self, title, matchClass=False)
Close the specified window gracefully
source code
 
resize_move(self, title, xOrigin=-1, yOrigin=-1, width=-1, height=-1, matchClass=False)
Resize and/or move the specified window
source code
 
move_to_desktop(self, title, deskNum, matchClass=False)
Move the specified window to the given desktop
source code
 
switch_desktop(self, deskNum)
Switch to the specified desktop
source code
 
set_property(self, title, action, prop, matchClass=False)
Set a property on the given window using the specified action
source code
tuple(int, int, int, int)
get_active_geometry(self)
Get the geometry of the currently active window
source code
str
get_active_title(self)
Get the visible title of the currently active window
source code
str
get_active_class(self)
Get the class of the currently active window
source code
 
__runWmctrl(self, args) source code
Method Details [hide private]

wait_for_focus(self, title, timeOut=5)

source code 

Wait for window with the given title to have focus

Usage: window.wait_for_focus(title, timeOut=5)

If the window becomes active, returns True. Otherwise, returns False if the window has not become active by the time the timeout has elapsed.

Parameters:
  • title - title to match against (as a regular expression)
  • timeOut - period (seconds) to wait before giving up
Returns: boolean

wait_for_exist(self, title, timeOut=5)

source code 

Wait for window with the given title to be created

Usage: window.wait_for_exist(title, timeOut=5)

If the window is in existence, returns True. Otherwise, returns False if the window has not been created by the time the timeout has elapsed.

Parameters:
  • title - title to match against (as a regular expression)
  • timeOut - period (seconds) to wait before giving up
Returns: boolean

activate(self, title, switchDesktop=False, matchClass=False)

source code 

Activate the specified window, giving it input focus

Usage: window.activate(title, switchDesktop=False, matchClass=False)

If switchDesktop is False (default), the window will be moved to the current desktop and activated. Otherwise, switch to the window's current desktop and activate it there.

Parameters:
  • title - window title to match against (as case-insensitive substring match)
  • switchDesktop - whether or not to switch to the window's current desktop
  • matchClass - if True, match on the window class instead of the title

close(self, title, matchClass=False)

source code 

Close the specified window gracefully

Usage: window.close(title, matchClass=False)

Parameters:
  • title - window title to match against (as case-insensitive substring match)
  • matchClass - if True, match on the window class instead of the title

resize_move(self, title, xOrigin=-1, yOrigin=-1, width=-1, height=-1, matchClass=False)

source code 

Resize and/or move the specified window

Usage: window.close(title, xOrigin=-1, yOrigin=-1, width=-1, height=-1, matchClass=False)

Leaving and of the position/dimension values as the default (-1) will cause that value to be left unmodified.

Parameters:
  • title - window title to match against (as case-insensitive substring match)
  • xOrigin - new x origin of the window (upper left corner)
  • yOrigin - new y origin of the window (upper left corner)
  • width - new width of the window
  • height - new height of the window
  • matchClass - if True, match on the window class instead of the title

move_to_desktop(self, title, deskNum, matchClass=False)

source code 

Move the specified window to the given desktop

Usage: window.move_to_desktop(title, deskNum, matchClass=False)

Parameters:
  • title - window title to match against (as case-insensitive substring match)
  • deskNum - desktop to move the window to (note: zero based)
  • matchClass - if True, match on the window class instead of the title

switch_desktop(self, deskNum)

source code 

Switch to the specified desktop

Usage: window.switch_desktop(deskNum)

Parameters:
  • deskNum - desktop to switch to (note: zero based)

set_property(self, title, action, prop, matchClass=False)

source code 

Set a property on the given window using the specified action

Usage: window.set_property(title, action, prop, matchClass=False)

Allowable actions: add, remove, toggle Allowable properties: modal, sticky, maximized_vert, maximized_horz, shaded, skip_taskbar, skip_pager, hidden, fullscreen, above

Parameters:
  • title - window title to match against (as case-insensitive substring match)
  • action - one of the actions listed above
  • prop - one of the properties listed above
  • matchClass - if True, match on the window class instead of the title

get_active_geometry(self)

source code 

Get the geometry of the currently active window

Usage: window.get_active_geometry()

Returns: tuple(int, int, int, int)
a 4-tuple containing the x-origin, y-origin, width and height of the window (in pixels)

get_active_title(self)

source code 

Get the visible title of the currently active window

Usage: window.get_active_title()

Returns: str
the visible title of the currentle active window

get_active_class(self)

source code 

Get the class of the currently active window

Usage: window.get_active_class()

Returns: str
the class of the currentle active window

autokey-0.90.4/doc/scripting/module-tree.html0000664000175000017500000000700311744235773020165 0ustar chrischris Module Hierarchy
 
[hide private]
[frames] | no frames]
[ Module Hierarchy | Class Hierarchy ]

Module Hierarchy

autokey-0.90.4/doc/scripting/toc.html0000664000175000017500000000237711361556017016531 0ustar chrischris Table of Contents

Table of Contents


Everything

Modules

lib.scripting

[hide private] autokey-0.90.4/doc/scripting/identifier-index.html0000664000175000017500000007217011744235773021201 0ustar chrischris Identifier Index
 
[hide private]
[frames] | no frames]

Identifier Index

[ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z _ ]

A

C

E

F

G

I

K

L

M

O

P

Q

R

S

W

_



autokey-0.90.4/doc/scripting/help.html0000664000175000017500000002474511744235773016707 0ustar chrischris Help
 
[hide private]
[frames] | no frames]

API Documentation

This document contains the API (Application Programming Interface) documentation for this project. Documentation for the Python objects defined by the project is divided into separate pages for each package, module, and class. The API documentation also includes two pages containing information about the project as a whole: a trees page, and an index page.

Object Documentation

Each Package Documentation page contains:

  • A description of the package.
  • A list of the modules and sub-packages contained by the package.
  • A summary of the classes defined by the package.
  • A summary of the functions defined by the package.
  • A summary of the variables defined by the package.
  • A detailed description of each function defined by the package.
  • A detailed description of each variable defined by the package.

Each Module Documentation page contains:

  • A description of the module.
  • A summary of the classes defined by the module.
  • A summary of the functions defined by the module.
  • A summary of the variables defined by the module.
  • A detailed description of each function defined by the module.
  • A detailed description of each variable defined by the module.

Each Class Documentation page contains:

  • A class inheritance diagram.
  • A list of known subclasses.
  • A description of the class.
  • A summary of the methods defined by the class.
  • A summary of the instance variables defined by the class.
  • A summary of the class (static) variables defined by the class.
  • A detailed description of each method defined by the class.
  • A detailed description of each instance variable defined by the class.
  • A detailed description of each class (static) variable defined by the class.

Project Documentation

The Trees page contains the module and class hierarchies:

  • The module hierarchy lists every package and module, with modules grouped into packages. At the top level, and within each package, modules and sub-packages are listed alphabetically.
  • The class hierarchy lists every class, grouped by base class. If a class has more than one base class, then it will be listed under each base class. At the top level, and under each base class, classes are listed alphabetically.

The Index page contains indices of terms and identifiers:

  • The term index lists every term indexed by any object's documentation. For each term, the index provides links to each place where the term is indexed.
  • The identifier index lists the (short) name of every package, module, class, method, function, variable, and parameter. For each identifier, the index provides a short description, and a link to its documentation.

The Table of Contents

The table of contents occupies the two frames on the left side of the window. The upper-left frame displays the project contents, and the lower-left frame displays the module contents:

Project
Contents
...
API
Documentation
Frame


Module
Contents
 
...
 

The project contents frame contains a list of all packages and modules that are defined by the project. Clicking on an entry will display its contents in the module contents frame. Clicking on a special entry, labeled "Everything," will display the contents of the entire project.

The module contents frame contains a list of every submodule, class, type, exception, function, and variable defined by a module or package. Clicking on an entry will display its documentation in the API documentation frame. Clicking on the name of the module, at the top of the frame, will display the documentation for the module itself.

The "frames" and "no frames" buttons below the top navigation bar can be used to control whether the table of contents is displayed or not.

The Navigation Bar

A navigation bar is located at the top and bottom of every page. It indicates what type of page you are currently viewing, and allows you to go to related pages. The following table describes the labels on the navigation bar. Note that not some labels (such as [Parent]) are not displayed on all pages.

Label Highlighted when... Links to...
[Parent] (never highlighted) the parent of the current package
[Package] viewing a package the package containing the current object
[Module] viewing a module the module containing the current object
[Class] viewing a class the class containing the current object
[Trees] viewing the trees page the trees page
[Index] viewing the index page the index page
[Help] viewing the help page the help page

The "show private" and "hide private" buttons below the top navigation bar can be used to control whether documentation for private objects is displayed. Private objects are usually defined as objects whose (short) names begin with a single underscore, but do not end with an underscore. For example, "_x", "__pprint", and "epydoc.epytext._tokenize" are private objects; but "re.sub", "__init__", and "type_" are not. However, if a module defines the "__all__" variable, then its contents are used to decide which objects are private.

A timestamp below the bottom navigation bar indicates when each page was last updated.

autokey-0.90.4/doc/scripting/crarr.png0000664000175000017500000000052411244635015016661 0ustar chrischris‰PNG  IHDR e¢E,tEXtCreation TimeTue 22 Aug 2006 00:43:10 -0500` XtIMEÖ)Ó}Ö pHYsÂÂnÐu>gAMA± üaEPLTEÿÿÿÍð×ÏÀ€f4sW áÛЊrD`@bCÜÕÈéäÜ–X{`,¯Ÿ€lN‡o@õóðª™xdEðí螊dÐÆ´”~TÖwÅvtRNS@æØfMIDATxÚc`@¼ì¼0&+š—Šˆ°»(’ˆ€ ;; /ðEXùØ‘?Ð n ƒª†— b;'ª+˜˜YÐ#œ(r<£"IEND®B`‚autokey-0.90.4/doc/scripting/lib.scripting-module.html0000664000175000017500000002065411744235773022004 0ustar chrischris lib.scripting
Package lib :: Module scripting
[hide private]
[frames] | no frames]

Module scripting

source code

Classes [hide private]
  Keyboard
Provides access to the keyboard for event generation.
  Mouse
Provides access to send mouse clicks
  Store
Allows persistent storage of values between invocations of the script.
  QtDialog
Provides a simple interface for the display of some basic dialogs to collect information from the user.
  System
Simplified access to some system commands.
  GtkDialog
Provides a simple interface for the display of some basic dialogs to collect information from the user.
  QtClipboard
Read/write access to the X selection and clipboard - QT version
  GtkClipboard
Read/write access to the X selection and clipboard - GTK version
  Window
Basic window management using wmctrl
  Engine
Provides access to the internals of AutoKey.
Variables [hide private]
  __package__ = 'lib'
autokey-0.90.4/doc/scripting/epydoc.js0000664000175000017500000002452511361570551016675 0ustar chrischrisfunction toggle_private() { // Search for any private/public links on this page. Store // their old text in "cmd," so we will know what action to // take; and change their text to the opposite action. var cmd = "?"; var elts = document.getElementsByTagName("a"); for(var i=0; i...
"; elt.innerHTML = s; } } function toggle(id) { elt = document.getElementById(id+"-toggle"); if (elt.innerHTML == "-") collapse(id); else expand(id); return false; } function highlight(id) { var elt = document.getElementById(id+"-def"); if (elt) elt.className = "py-highlight-hdr"; var elt = document.getElementById(id+"-expanded"); if (elt) elt.className = "py-highlight"; var elt = document.getElementById(id+"-collapsed"); if (elt) elt.className = "py-highlight"; } function num_lines(s) { var n = 1; var pos = s.indexOf("\n"); while ( pos > 0) { n += 1; pos = s.indexOf("\n", pos+1); } return n; } // Collapse all blocks that mave more than `min_lines` lines. function collapse_all(min_lines) { var elts = document.getElementsByTagName("div"); for (var i=0; i 0) if (elt.id.substring(split, elt.id.length) == "-expanded") if (num_lines(elt.innerHTML) > min_lines) collapse(elt.id.substring(0, split)); } } function expandto(href) { var start = href.indexOf("#")+1; if (start != 0 && start != href.length) { if (href.substring(start, href.length) != "-") { collapse_all(4); pos = href.indexOf(".", start); while (pos != -1) { var id = href.substring(start, pos); expand(id); pos = href.indexOf(".", pos+1); } var id = href.substring(start, href.length); expand(id); highlight(id); } } } function kill_doclink(id) { var parent = document.getElementById(id); parent.removeChild(parent.childNodes.item(0)); } function auto_kill_doclink(ev) { if (!ev) var ev = window.event; if (!this.contains(ev.toElement)) { var parent = document.getElementById(this.parentID); parent.removeChild(parent.childNodes.item(0)); } } function doclink(id, name, targets_id) { var elt = document.getElementById(id); // If we already opened the box, then destroy it. // (This case should never occur, but leave it in just in case.) if (elt.childNodes.length > 1) { elt.removeChild(elt.childNodes.item(0)); } else { // The outer box: relative + inline positioning. var box1 = document.createElement("div"); box1.style.position = "relative"; box1.style.display = "inline"; box1.style.top = 0; box1.style.left = 0; // A shadow for fun var shadow = document.createElement("div"); shadow.style.position = "absolute"; shadow.style.left = "-1.3em"; shadow.style.top = "-1.3em"; shadow.style.background = "#404040"; // The inner box: absolute positioning. var box2 = document.createElement("div"); box2.style.position = "relative"; box2.style.border = "1px solid #a0a0a0"; box2.style.left = "-.2em"; box2.style.top = "-.2em"; box2.style.background = "white"; box2.style.padding = ".3em .4em .3em .4em"; box2.style.fontStyle = "normal"; box2.onmouseout=auto_kill_doclink; box2.parentID = id; // Get the targets var targets_elt = document.getElementById(targets_id); var targets = targets_elt.getAttribute("targets"); var links = ""; target_list = targets.split(","); for (var i=0; i" + target[0] + ""; } // Put it all together. elt.insertBefore(box1, elt.childNodes.item(0)); //box1.appendChild(box2); box1.appendChild(shadow); shadow.appendChild(box2); box2.innerHTML = "Which "+name+" do you want to see documentation for?" + ""; } return false; } function get_anchor() { var href = location.href; var start = href.indexOf("#")+1; if ((start != 0) && (start != href.length)) return href.substring(start, href.length); } function redirect_url(dottedName) { // Scan through each element of the "pages" list, and check // if "name" matches with any of them. for (var i=0; i-m" or "-c"; // extract the portion & compare it to dottedName. var pagename = pages[i].substring(0, pages[i].length-2); if (pagename == dottedName.substring(0,pagename.length)) { // We've found a page that matches `dottedName`; // construct its URL, using leftover `dottedName` // content to form an anchor. var pagetype = pages[i].charAt(pages[i].length-1); var url = pagename + ((pagetype=="m")?"-module.html": "-class.html"); if (dottedName.length > pagename.length) url += "#" + dottedName.substring(pagename.length+1, dottedName.length); return url; } } } autokey-0.90.4/doc/scripting/lib.scripting.System-class.html0000664000175000017500000002317311744235773023106 0ustar chrischris lib.scripting.System
Package lib :: Module scripting :: Class System
[hide private]
[frames] | no frames]

Class System

source code

Simplified access to some system commands.

Instance Methods [hide private]
 
exec_command(self, command, getOutput=True)
Execute a shell command
source code
 
create_file(self, fileName, contents='')
Create a file with contents
source code
Method Details [hide private]

exec_command(self, command, getOutput=True)

source code 

Execute a shell command

Usage: system.exec_command(command, getOutput=True)

Set getOutput to False if the command does not exit and return immediately. Otherwise AutoKey will not respond to any hotkeys/abbreviations etc until the process started by the command exits.

Parameters:
  • command - command to be executed (including any arguments) - e.g. "ls -l"
  • getOutput - whether to capture the (stdout) output of the command
Raises:
  • subprocess.CalledProcessError - if the command returns a non-zero exit code

create_file(self, fileName, contents='')

source code 

Create a file with contents

Usage: system.create_file(fileName, contents="")

Parameters:
  • fileName - full path to the file to be created
  • contents - contents to insert into the file

autokey-0.90.4/src/lib/macro.py0000664000175000017500000000775611736014276015327 0ustar chrischrisfrom iomediator import KEY_SPLIT_RE, Key import common if common.USING_QT: from PyKDE4.kdecore import ki18n from PyKDE4.kdeui import KMenu, KAction from PyQt4.QtCore import SIGNAL _ = ki18n class MacroAction(KAction): def __init__(self, menu, macro, callback): KAction.__init__(self, macro.TITLE.toString(), menu) self.macro = macro self.callback = callback self.connect(self, SIGNAL("triggered()"), self.on_triggered) def on_triggered(self): self.callback(self.macro) else: from gi.repository import Gtk class MacroManager: def __init__(self, engine): self.macros = [] self.macros.append(ScriptMacro(engine)) self.macros.append(DateMacro()) self.macros.append(FileContentsMacro()) self.macros.append(CursorMacro()) def get_menu(self, callback, menu=None): if common.USING_QT: for macro in self.macros: menu.addAction(MacroAction(menu, macro, callback)) else: menu = Gtk.Menu() for macro in self.macros: menuItem = Gtk.MenuItem(macro.TITLE) menuItem.connect("activate", callback, macro) menu.append(menuItem) menu.show_all() return menu def process_expansion(self, expansion): parts = KEY_SPLIT_RE.split(expansion.string) for macro in self.macros: macro.process(parts) expansion.string = ''.join(parts) class AbstractMacro: def get_token(self): ret = "<%s" % self.ID if len(self.ARGS) == 0: ret += ">" else: for k, v in self.ARGS: ret += " " ret += k ret += "=" ret += ">" return ret def _can_process(self, token): if KEY_SPLIT_RE.match(token): return token[1:-1].split(' ', 1)[0] == self.ID else: return False def _get_args(self, token): l = token[:-1].split(' ') ret = {} if len(l) > 1: for arg in l[1:]: key, val = arg.split('=', 1) ret[key] = val for k, v in self.ARGS: if k not in ret: raise Exception("Missing mandatory argument '%s' for macro '%s'" % (k, self.ID)) return ret def process(self, parts): for i in xrange(len(parts)): if self._can_process(parts[i]): self.do_process(parts, i) class CursorMacro(AbstractMacro): ID = "cursor" TITLE = _("Position cursor") ARGS = [] def do_process(self, parts, i): try: lefts = len(''.join(parts[i+1:])) parts.append(Key.LEFT * lefts) parts[i] = '' except IndexError: pass class ScriptMacro(AbstractMacro): ID = "script" TITLE = _("Run script") ARGS = [("name", _("Name")), ("args", _("Arguments (comma separated)"))] def __init__(self, engine): self.engine = engine def do_process(self, parts, i): args = self._get_args(parts[i]) self.engine.run_script_from_macro(args) parts[i] = self.engine.get_return_value() class DateMacro(AbstractMacro): ID = "date" TITLE = _("Insert date") ARGS = [("format", _("Format"))] def do_process(self, parts, i): format = self._get_args(parts[i])["format"] date = datetime.datetime.now().strftime(format) parts[i] = date class FileContentsMacro(AbstractMacro): ID = "file" TITLE = _("Insert file contents") ARGS = [("name", _("File name"))] def do_process(self, parts, i): name = self._get_args(parts[i])["name"] with open(name, "r") as inputFile: parts[i] = inputFile.read() autokey-0.90.4/src/lib/scripting.py0000664000175000017500000013220411754436675016226 0ustar chrischris# -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import subprocess, threading, time, re import common, model, iomediator if common.USING_QT: from PyQt4.QtGui import QClipboard, QApplication else: from gi.repository import Gtk, Gdk class Keyboard: """ Provides access to the keyboard for event generation. """ def __init__(self, mediator): self.mediator = mediator def send_keys(self, keyString): """ Send a sequence of keys via keyboard events Usage: C{keyboard.send_keys(keyString)} @param keyString: string of keys (including special keys) to send """ self.mediator.interface.begin_send() self.mediator.send_string(keyString.decode("utf-8")) self.mediator.interface.finish_send() def send_key(self, key, repeat=1): """ Send a keyboard event Usage: C{keyboard.send_key(key, repeat=1)} @param key: they key to be sent (e.g. "s" or "") @param repeat: number of times to repeat the key event """ for x in xrange(repeat): self.mediator.send_key(key.decode("utf-8")) self.mediator.flush() def press_key(self, key): """ Send a key down event Usage: C{keyboard.press_key(key)} The key will be treated as down until a matching release_key() is sent. @param key: they key to be pressed (e.g. "s" or "") """ self.mediator.press_key(key.decode("utf-8")) def release_key(self, key): """ Send a key up event Usage: C{keyboard.release_key(key)} If the specified key was not made down using press_key(), the event will be ignored. @param key: they key to be released (e.g. "s" or "") """ self.mediator.release_key(key.decode("utf-8")) def fake_keypress(self, key, repeat=1): """ Fake a keypress Usage: C{keyboard.fake_keypress(key, repeat=1)} Uses XTest to 'fake' a keypress. This is useful to send keypresses to some applications which won't respond to keyboard.send_key() @param key: they key to be sent (e.g. "s" or "") @param repeat: number of times to repeat the key event """ for x in xrange(repeat): self.mediator.fake_keypress(key.decode("utf-8")) def wait_for_keypress(self, key, modifiers=[], timeOut=10.0): """ Wait for a keypress or key combination Usage: C{keyboard.wait_for_keypress(self, key, modifiers=[], timeOut=10.0)} Note: this function cannot be used to wait for modifier keys on their own @param key: they key to wait for @param modifiers: list of modifiers that should be pressed with the key @param timeOut: maximum time, in seconds, to wait for the keypress to occur """ w = iomediator.Waiter(key, modifiers, None, timeOut) w.wait() class Mouse: """ Provides access to send mouse clicks """ def __init__(self, mediator): self.mediator = mediator def click_relative(self, x, y, button): """ Send a mouse click relative to the active window Usage: C{mouse.click_relative(x, y, button)} @param x: x-coordinate in pixels, relative to upper left corner of window @param y: y-coordinate in pixels, relative to upper left corner of window @param button: mouse button to simulate (left=1, middle=2, right=3) """ self.mediator.send_mouse_click(x, y, button, True) def click_relative_self(self, x, y, button): """ Send a mouse click relative to the current mouse position Usage: C{mouse.click_relative_self(x, y, button)} @param x: x-offset in pixels, relative to current mouse position @param y: y-offset in pixels, relative to current mouse position @param button: mouse button to simulate (left=1, middle=2, right=3) """ self.mediator.send_mouse_click_relative(x, y, button) def click_absolute(self, x, y, button): """ Send a mouse click relative to the screen (absolute) Usage: C{mouse.click_absolute(x, y, button)} @param x: x-coordinate in pixels, relative to upper left corner of window @param y: y-coordinate in pixels, relative to upper left corner of window @param button: mouse button to simulate (left=1, middle=2, right=3) """ self.mediator.send_mouse_click(x, y, button, False) def wait_for_click(self, button, timeOut=10.0): """ Wait for a mouse click Usage: C{mouse.wait_for_click(self, button, timeOut=10.0)} @param button: they mouse button click to wait for as a button number, 1-9 @param timeOut: maximum time, in seconds, to wait for the keypress to occur """ button = int(button) w = iomediator.Waiter(None, None, button, timeOut) w.wait() class Store(dict): """ Allows persistent storage of values between invocations of the script. """ def set_value(self, key, value): """ Store a value Usage: C{store.set_value(key, value)} """ self[key] = value def get_value(self, key): """ Get a value Usage: C{store.get_value(key)} """ return self[key] def remove_value(self, key): """ Remove a value Usage: C{store.remove_value(key)} """ del self[key] def set_global_value(self, key, value): """ Store a global value Usage: C{store.set_global_value(key, value)} The value stored with this method will be available to all scripts. """ Store.GLOBALS[key] = value def get_global_value(self, key): """ Get a global value Usage: C{store.get_global_value(key)} """ return self.GLOBALS[key] def remove_global_value(self, key): """ Remove a global value Usage: C{store.remove_global_value(key)} """ del self.GLOBALS[key] class QtDialog: """ Provides a simple interface for the display of some basic dialogs to collect information from the user. This version uses KDialog to integrate well with KDE. To pass additional arguments to KDialog that are not specifically handled, use keyword arguments. For example, to pass the --geometry argument to KDialog to specify the desired size of the dialog, pass C{geometry="700x400"} as one of the parameters. All keyword arguments must be given as strings. A note on exit codes: an exit code of 0 indicates that the user clicked OK. """ def __runKdialog(self, title, args, kwargs): for k, v in kwargs.iteritems(): args.append("--" + k) args.append(v) p = subprocess.Popen(["kdialog", "--title", title] + args, stdout=subprocess.PIPE) retCode = p.wait() output = p.stdout.read()[:-1] # Drop trailing newline return (retCode, output) def info_dialog(self, title="Information", message="", **kwargs): """ Show an information dialog Usage: C{dialog.info_dialog(title="Information", message="", **kwargs)} @param title: window title for the dialog @param message: message displayed in the dialog @return: a tuple containing the exit code and user input @rtype: C{tuple(int, str)} """ return self.__runKdialog(title, ["--msgbox", message], kwargs) def input_dialog(self, title="Enter a value", message="Enter a value", default="", **kwargs): """ Show an input dialog Usage: C{dialog.input_dialog(title="Enter a value", message="Enter a value", default="", **kwargs)} @param title: window title for the dialog @param message: message displayed above the input box @param default: default value for the input box @return: a tuple containing the exit code and user input @rtype: C{tuple(int, str)} """ return self.__runKdialog(title, ["--inputbox", message, default], kwargs) def password_dialog(self, title="Enter password", message="Enter password", **kwargs): """ Show a password input dialog Usage: C{dialog.password_dialog(title="Enter password", message="Enter password", **kwargs)} @param title: window title for the dialog @param message: message displayed above the password input box @return: a tuple containing the exit code and user input @rtype: C{tuple(int, str)} """ return self.__runKdialog(title, ["--password", message], kwargs) def combo_menu(self, options, title="Choose an option", message="Choose an option", **kwargs): """ Show a combobox menu Usage: C{dialog.combo_menu(options, title="Choose an option", message="Choose an option", **kwargs)} @param options: list of options (strings) for the dialog @param title: window title for the dialog @param message: message displayed above the combobox @return: a tuple containing the exit code and user choice @rtype: C{tuple(int, str)} """ return self.__runKdialog(title, ["--combobox", message] + options, kwargs) def list_menu(self, options, title="Choose a value", message="Choose a value", default=None, **kwargs): """ Show a single-selection list menu Usage: C{dialog.list_menu(options, title="Choose a value", message="Choose a value", default=None, **kwargs)} @param options: list of options (strings) for the dialog @param title: window title for the dialog @param message: message displayed above the list @param default: default value to be selected @return: a tuple containing the exit code and user choice @rtype: C{tuple(int, str)} """ choices = [] optionNum = 0 for option in options: choices.append(str(optionNum)) choices.append(option) if option == default: choices.append("on") else: choices.append("off") optionNum += 1 retCode, result = self.__runKdialog(title, ["--radiolist", message] + choices, kwargs) choice = options[int(result)] return retCode, choice def list_menu_multi(self, options, title="Choose one or more values", message="Choose one or more values", defaults=[], **kwargs): """ Show a multiple-selection list menu Usage: C{dialog.list_menu_multi(options, title="Choose one or more values", message="Choose one or more values", defaults=[], **kwargs)} @param options: list of options (strings) for the dialog @param title: window title for the dialog @param message: message displayed above the list @param defaults: list of default values to be selected @return: a tuple containing the exit code and user choice @rtype: C{tuple(int, str)} """ choices = [] optionNum = 0 for option in options: choices.append(str(optionNum)) choices.append(option) if option in defaults: choices.append("on") else: choices.append("off") optionNum += 1 retCode, output = self.__runKdialog(title, ["--separate-output", "--checklist", message] + choices, kwargs) results = output.split() choices = [] for index in results: choices.append(options[int(index)]) return retCode, choices def open_file(self, title="Open File", initialDir="~", fileTypes="*|All Files", rememberAs=None, **kwargs): """ Show an Open File dialog Usage: C{dialog.open_file(title="Open File", initialDir="~", fileTypes="*|All Files", rememberAs=None, **kwargs)} @param title: window title for the dialog @param initialDir: starting directory for the file dialog @param fileTypes: file type filter expression @param rememberAs: gives an ID to this file dialog, allowing it to open at the last used path next time @return: a tuple containing the exit code and file path @rtype: C{tuple(int, str)} """ if rememberAs is not None: return self.__runKdialog(title, ["--getopenfilename", initialDir, fileTypes, ":" + rememberAs], kwargs) else: return self.__runKdialog(title, ["--getopenfilename", initialDir, fileTypes], kwargs) def save_file(self, title="Save As", initialDir="~", fileTypes="*|All Files", rememberAs=None, **kwargs): """ Show a Save As dialog Usage: C{dialog.save_file(title="Save As", initialDir="~", fileTypes="*|All Files", rememberAs=None, **kwargs)} @param title: window title for the dialog @param initialDir: starting directory for the file dialog @param fileTypes: file type filter expression @param rememberAs: gives an ID to this file dialog, allowing it to open at the last used path next time @return: a tuple containing the exit code and file path @rtype: C{tuple(int, str)} """ if rememberAs is not None: return self.__runKdialog(title, ["--getsavefilename", initialDir, fileTypes, ":" + rememberAs], kwargs) else: return self.__runKdialog(title, ["--getsavefilename", initialDir, fileTypes], kwargs) def choose_directory(self, title="Select Directory", initialDir="~", rememberAs=None, **kwargs): """ Show a Directory Chooser dialog Usage: C{dialog.choose_directory(title="Select Directory", initialDir="~", rememberAs=None, **kwargs)} @param title: window title for the dialog @param initialDir: starting directory for the directory chooser dialog @param rememberAs: gives an ID to this file dialog, allowing it to open at the last used path next time @return: a tuple containing the exit code and chosen path @rtype: C{tuple(int, str)} """ if rememberAs is not None: return self.__runKdialog(title, ["--getexistingdirectory", initialDir, ":" + rememberAs], kwargs) else: return self.__runKdialog(title, ["--getexistingdirectory", initialDir], kwargs) def choose_colour(self, title="Select Colour", **kwargs): """ Show a Colour Chooser dialog Usage: C{dialog.choose_colour(title="Select Colour")} @param title: window title for the dialog @return: a tuple containing the exit code and colour @rtype: C{tuple(int, str)} """ return self.__runKdialog(title, ["--getcolor"], kwargs) def calendar(self, title="Choose a date", format="%Y-%m-%d", date="today", **kwargs): """ Show a calendar dialog Usage: C{dialog.calendar_dialog(title="Choose a date", format="%Y-%m-%d", date="YYYY-MM-DD", **kwargs)} Note: the format and date parameters are not currently used @param title: window title for the dialog @param format: format of date to be returned @param date: initial date as YYYY-MM-DD, otherwise today @return: a tuple containing the exit code and date @rtype: C{tuple(int, str)} """ return self.__runKdialog(title, ["--calendar"], kwargs) class System: """ Simplified access to some system commands. """ def exec_command(self, command, getOutput=True): """ Execute a shell command Usage: C{system.exec_command(command, getOutput=True)} Set getOutput to False if the command does not exit and return immediately. Otherwise AutoKey will not respond to any hotkeys/abbreviations etc until the process started by the command exits. @param command: command to be executed (including any arguments) - e.g. "ls -l" @param getOutput: whether to capture the (stdout) output of the command @raise subprocess.CalledProcessError: if the command returns a non-zero exit code """ if getOutput: p = subprocess.Popen(command, shell=True, bufsize=-1, stdout=subprocess.PIPE) retCode = p.wait() output = p.stdout.read()[:-1] if retCode != 0: raise subprocess.CalledProcessError(retCode, output) else: return output else: subprocess.Popen(command, shell=True, bufsize=-1) def create_file(self, fileName, contents=""): """ Create a file with contents Usage: C{system.create_file(fileName, contents="")} @param fileName: full path to the file to be created @param contents: contents to insert into the file """ f = open(fileName, "w") f.write(contents) f.close() class GtkDialog: """ Provides a simple interface for the display of some basic dialogs to collect information from the user. This version uses Zenity to integrate well with GNOME. To pass additional arguments to Zenity that are not specifically handled, use keyword arguments. For example, to pass the --timeout argument to Zenity pass C{timeout="15"} as one of the parameters. All keyword arguments must be given as strings. A note on exit codes: an exit code of 0 indicates that the user clicked OK. """ def __runZenity(self, title, args, kwargs): for k, v in kwargs.iteritems(): args.append("--" + k) args.append(v) p = subprocess.Popen(["zenity", "--title", title] + args, stdout=subprocess.PIPE) retCode = p.wait() output = p.stdout.read()[:-1] # Drop trailing newline return (retCode, output) def info_dialog(self, title="Information", message="", **kwargs): """ Show an information dialog Usage: C{dialog.info_dialog(title="Information", message="", **kwargs)} @param title: window title for the dialog @param message: message displayed in the dialog @return: a tuple containing the exit code and user input @rtype: C{tuple(int, str)} """ return self.__runZenity(title, ["--info", "--text", message], kwargs) def input_dialog(self, title="Enter a value", message="Enter a value", default="", **kwargs): """ Show an input dialog Usage: C{dialog.input_dialog(title="Enter a value", message="Enter a value", default="", **kwargs)} @param title: window title for the dialog @param message: message displayed above the input box @param default: default value for the input box @return: a tuple containing the exit code and user input @rtype: C{tuple(int, str)} """ return self.__runZenity(title, ["--entry", "--text", message, "--entry-text", default], kwargs) def password_dialog(self, title="Enter password", message="Enter password", **kwargs): """ Show a password input dialog Usage: C{dialog.password_dialog(title="Enter password", message="Enter password")} @param title: window title for the dialog @param message: message displayed above the password input box @return: a tuple containing the exit code and user input @rtype: C{tuple(int, str)} """ return self.__runZenity(title, ["--entry", "--text", message, "--hide-text"], kwargs) #def combo_menu(self, options, title="Choose an option", message="Choose an option"): """ Show a combobox menu - not supported by zenity Usage: C{dialog.combo_menu(options, title="Choose an option", message="Choose an option")} @param options: list of options (strings) for the dialog @param title: window title for the dialog @param message: message displayed above the combobox """ #return self.__runZenity(title, ["--combobox", message] + options) def list_menu(self, options, title="Choose a value", message="Choose a value", default=None, **kwargs): """ Show a single-selection list menu Usage: C{dialog.list_menu(options, title="Choose a value", message="Choose a value", default=None, **kwargs)} @param options: list of options (strings) for the dialog @param title: window title for the dialog @param message: message displayed above the list @param default: default value to be selected @return: a tuple containing the exit code and user choice @rtype: C{tuple(int, str)} """ choices = [] #optionNum = 0 for option in options: if option == default: choices.append("TRUE") else: choices.append("FALSE") #choices.append(str(optionNum)) choices.append(option) #optionNum += 1 return self.__runZenity(title, ["--list", "--radiolist", "--text", message, "--column", " ", "--column", "Options"] + choices, kwargs) #return retCode, choice def list_menu_multi(self, options, title="Choose one or more values", message="Choose one or more values", defaults=[], **kwargs): """ Show a multiple-selection list menu Usage: C{dialog.list_menu_multi(options, title="Choose one or more values", message="Choose one or more values", defaults=[], **kwargs)} @param options: list of options (strings) for the dialog @param title: window title for the dialog @param message: message displayed above the list @param defaults: list of default values to be selected @return: a tuple containing the exit code and user choice @rtype: C{tuple(int, str)} """ choices = [] #optionNum = 0 for option in options: if option in defaults: choices.append("TRUE") else: choices.append("FALSE") #choices.append(str(optionNum)) choices.append(option) #optionNum += 1 retCode, output = self.__runZenity(title, ["--list", "--checklist", "--text", message, "--column", " ", "--column", "Options"] + choices, kwargs) results = output.split('|') #choices = [] #for choice in results: # choices.append(choice) return retCode, results def open_file(self, title="Open File", **kwargs): """ Show an Open File dialog Usage: C{dialog.open_file(title="Open File", **kwargs)} @param title: window title for the dialog @return: a tuple containing the exit code and file path @rtype: C{tuple(int, str)} """ #if rememberAs is not None: # return self.__runZenity(title, ["--getopenfilename", initialDir, fileTypes, ":" + rememberAs]) #else: return self.__runZenity(title, ["--file-selection"], kwargs) def save_file(self, title="Save As", **kwargs): """ Show a Save As dialog Usage: C{dialog.save_file(title="Save As", **kwargs)} @param title: window title for the dialog @return: a tuple containing the exit code and file path @rtype: C{tuple(int, str)} """ #if rememberAs is not None: # return self.__runZenity(title, ["--getsavefilename", initialDir, fileTypes, ":" + rememberAs]) #else: return self.__runZenity(title, ["--file-selection", "--save"], kwargs) def choose_directory(self, title="Select Directory", initialDir="~", **kwargs): """ Show a Directory Chooser dialog Usage: C{dialog.choose_directory(title="Select Directory", **kwargs)} @param title: window title for the dialog @return: a tuple containing the exit code and path @rtype: C{tuple(int, str)} """ #if rememberAs is not None: # return self.__runZenity(title, ["--getexistingdirectory", initialDir, ":" + rememberAs]) #else: return self.__runZenity(title, ["--file-selection", "--directory"], kwargs) #def choose_colour(self, title="Select Colour"): """ Show a Colour Chooser dialog - not supported by zenity Usage: C{dialog.choose_colour(title="Select Colour")} @param title: window title for the dialog """ #return self.__runZenity(title, ["--getcolor"]) def calendar(self, title="Choose a date", format="%Y-%m-%d", date="today", **kwargs): """ Show a calendar dialog Usage: C{dialog.calendar_dialog(title="Choose a date", format="%Y-%m-%d", date="YYYY-MM-DD", **kwargs)} @param title: window title for the dialog @param format: format of date to be returned @param date: initial date as YYYY-MM-DD, otherwise today @return: a tuple containing the exit code and date @rtype: C{tuple(int, str)} """ if re.match(r"[0-9]{4}-[0-9]{2}-[0-9]{2}", date): year = date[0:4] month = date[5:7] day = date[8:10] date_args = ["--year=" + year, "--month=" + month, "--day=" + day] else: date_args = [] return self.__runZenity(title, ["--calendar", "--date-format=" + format] + date_args, kwargs) class QtClipboard: """ Read/write access to the X selection and clipboard - QT version """ def __init__(self, app): self.clipBoard = QApplication.clipboard() self.app = app def fill_selection(self, contents): """ Copy text into the X selection Usage: C{clipboard.fill_selection(contents)} @param contents: string to be placed in the selection """ self.__execAsync(self.__fillSelection, contents) def __fillSelection(self, string): self.clipBoard.setText(string, QClipboard.Selection) self.sem.release() def get_selection(self): """ Read text from the X selection Usage: C{clipboard.get_selection()} @return: text contents of the mouse selection @rtype: C{str} """ self.__execAsync(self.__getSelection) return unicode(self.text) def __getSelection(self): self.text = self.clipBoard.text(QClipboard.Selection) self.sem.release() def fill_clipboard(self, contents): """ Copy text into the clipboard Usage: C{clipboard.fill_clipboard(contents)} @param contents: string to be placed in the selection """ self.__execAsync(self.__fillClipboard, contents) def __fillClipboard(self, string): self.clipBoard.setText(string, QClipboard.Clipboard) self.sem.release() def get_clipboard(self): """ Read text from the clipboard Usage: C{clipboard.get_clipboard()} @return: text contents of the clipboard @rtype: C{str} """ self.__execAsync(self.__getClipboard) return unicode(self.text) def __getClipboard(self): self.text = self.clipBoard.text(QClipboard.Clipboard) self.sem.release() def __execAsync(self, callback, *args): self.sem = threading.Semaphore(0) self.app.exec_in_main(callback, *args) self.sem.acquire() class GtkClipboard: """ Read/write access to the X selection and clipboard - GTK version """ def __init__(self, app): self.clipBoard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) self.selection = Gtk.Clipboard.get(Gdk.SELECTION_PRIMARY) self.app = app def fill_selection(self, contents): """ Copy text into the X selection Usage: C{clipboard.fill_selection(contents)} @param contents: string to be placed in the selection """ #self.__execAsync(self.__fillSelection, contents) self.__fillSelection(contents) def __fillSelection(self, string): Gdk.threads_enter() self.selection.set_text(string.encode("utf-8")) Gdk.threads_leave() #self.sem.release() def get_selection(self): """ Read text from the X selection Usage: C{clipboard.get_selection()} @return: text contents of the mouse selection @rtype: C{str} @raise Exception: if no text was found in the selection """ Gdk.threads_enter() text = self.selection.wait_for_text() Gdk.threads_leave() if text is not None: return text.decode("utf-8") else: raise Exception("No text found in X selection") def fill_clipboard(self, contents): """ Copy text into the clipboard Usage: C{clipboard.fill_clipboard(contents)} @param contents: string to be placed in the selection """ Gdk.threads_enter() self.clipBoard.set_text(contents.encode("utf-8")) Gdk.threads_leave() def get_clipboard(self): """ Read text from the clipboard Usage: C{clipboard.get_clipboard()} @return: text contents of the clipboard @rtype: C{str} @raise Exception: if no text was found on the clipboard """ Gdk.threads_enter() text = self.clipBoard.wait_for_text() Gdk.threads_leave() if text is not None: return text.decode("utf-8") else: raise Exception("No text found on clipboard") class Window: """ Basic window management using wmctrl Note: in all cases where a window title is required (with the exception of wait_for_focus()), two special values of window title are permitted: :ACTIVE: - select the currently active window :SELECT: - select the desired window by clicking on it """ def __init__(self, mediator): self.mediator = mediator def wait_for_focus(self, title, timeOut=5): """ Wait for window with the given title to have focus Usage: C{window.wait_for_focus(title, timeOut=5)} If the window becomes active, returns True. Otherwise, returns False if the window has not become active by the time the timeout has elapsed. @param title: title to match against (as a regular expression) @param timeOut: period (seconds) to wait before giving up @rtype: boolean """ regex = re.compile(title) waited = 0 while waited <= timeOut: if regex.match(self.mediator.interface.get_window_title()): return True if timeOut == 0: break # zero length timeout, if not matched go straight to end time.sleep(0.3) waited += 0.3 return False def wait_for_exist(self, title, timeOut=5): """ Wait for window with the given title to be created Usage: C{window.wait_for_exist(title, timeOut=5)} If the window is in existence, returns True. Otherwise, returns False if the window has not been created by the time the timeout has elapsed. @param title: title to match against (as a regular expression) @param timeOut: period (seconds) to wait before giving up @rtype: boolean """ regex = re.compile(title) waited = 0 while waited <= timeOut: retCode, output = self.__runWmctrl(["-l"]) for line in output.split('\n'): if regex.match(line[14:].split(' ', 1)[-1]): return True if timeOut == 0: break # zero length timeout, if not matched go straight to end time.sleep(0.3) waited += 0.3 return False def activate(self, title, switchDesktop=False, matchClass=False): """ Activate the specified window, giving it input focus Usage: C{window.activate(title, switchDesktop=False, matchClass=False)} If switchDesktop is False (default), the window will be moved to the current desktop and activated. Otherwise, switch to the window's current desktop and activate it there. @param title: window title to match against (as case-insensitive substring match) @param switchDesktop: whether or not to switch to the window's current desktop @param matchClass: if True, match on the window class instead of the title """ if switchDesktop: args = ["-a", title] else: args = ["-R", title] if matchClass: args += ["-x"] self.__runWmctrl(args) def close(self, title, matchClass=False): """ Close the specified window gracefully Usage: C{window.close(title, matchClass=False)} @param title: window title to match against (as case-insensitive substring match) @param matchClass: if True, match on the window class instead of the title """ if matchClass: self.__runWmctrl(["-c", title, "-x"]) else: self.__runWmctrl(["-c", title]) def resize_move(self, title, xOrigin=-1, yOrigin=-1, width=-1, height=-1, matchClass=False): """ Resize and/or move the specified window Usage: C{window.close(title, xOrigin=-1, yOrigin=-1, width=-1, height=-1, matchClass=False)} Leaving and of the position/dimension values as the default (-1) will cause that value to be left unmodified. @param title: window title to match against (as case-insensitive substring match) @param xOrigin: new x origin of the window (upper left corner) @param yOrigin: new y origin of the window (upper left corner) @param width: new width of the window @param height: new height of the window @param matchClass: if True, match on the window class instead of the title """ mvArgs = ["0", str(xOrigin), str(yOrigin), str(width), str(height)] if matchClass: xArgs = ["-x"] else: xArgs = [] self.__runWmctrl(["-r", title, "-e", ','.join(mvArgs)] + xArgs) def move_to_desktop(self, title, deskNum, matchClass=False): """ Move the specified window to the given desktop Usage: C{window.move_to_desktop(title, deskNum, matchClass=False)} @param title: window title to match against (as case-insensitive substring match) @param deskNum: desktop to move the window to (note: zero based) @param matchClass: if True, match on the window class instead of the title """ if matchClass: xArgs = ["-x"] else: xArgs = [] self.__runWmctrl(["-r", title, "-t", str(deskNum)] + xArgs) def switch_desktop(self, deskNum): """ Switch to the specified desktop Usage: C{window.switch_desktop(deskNum)} @param deskNum: desktop to switch to (note: zero based) """ self.__runWmctrl(["-s", str(deskNum)]) def set_property(self, title, action, prop, matchClass=False): """ Set a property on the given window using the specified action Usage: C{window.set_property(title, action, prop, matchClass=False)} Allowable actions: C{add, remove, toggle} Allowable properties: C{modal, sticky, maximized_vert, maximized_horz, shaded, skip_taskbar, skip_pager, hidden, fullscreen, above} @param title: window title to match against (as case-insensitive substring match) @param action: one of the actions listed above @param prop: one of the properties listed above @param matchClass: if True, match on the window class instead of the title """ if matchClass: xArgs = ["-x"] else: xArgs = [] self.__runWmctrl(["-r", title, "-b" + action + ',' + prop] + xArgs) def get_active_geometry(self): """ Get the geometry of the currently active window Usage: C{window.get_active_geometry()} @return: a 4-tuple containing the x-origin, y-origin, width and height of the window (in pixels) @rtype: C{tuple(int, int, int, int)} """ active = self.mediator.interface.get_window_title() result, output = self.__runWmctrl(["-l", "-G"]) matchingLine = None for line in output.split('\n'): if active in line[34:].split(' ', 1)[-1]: matchingLine = line if matchingLine is not None: output = matchingLine.split()[2:6] return map(int, output) else: return None def get_active_title(self): """ Get the visible title of the currently active window Usage: C{window.get_active_title()} @return: the visible title of the currentle active window @rtype: C{str} """ return self.mediator.interface.get_window_title() def get_active_class(self): """ Get the class of the currently active window Usage: C{window.get_active_class()} @return: the class of the currentle active window @rtype: C{str} """ return self.mediator.interface.get_window_class() def __runWmctrl(self, args): p = subprocess.Popen(["wmctrl"] + args, stdout=subprocess.PIPE) retCode = p.wait() output = p.stdout.read()[:-1] # Drop trailing newline return (retCode, output) class Engine: """ Provides access to the internals of AutoKey. Note that any configuration changes made using this API while the configuration window is open will not appear until it is closed and re-opened. """ def __init__(self, configManager, runner): self.configManager = configManager self.runner = runner self.monitor = configManager.app.monitor self.__returnValue = '' def get_folder(self, title): """ Retrieve a folder by its title Usage: C{engine.get_folder(title)} Note that if more than one folder has the same title, only the first match will be returned. """ for folder in self.configManager.allFolders: if folder.title == title: return folder return None def create_phrase(self, folder, description, contents): """ Create a text phrase Usage: C{engine.create_phrase(folder, description, contents)} A new phrase with no abbreviation or hotkey is created in the specified folder @param folder: folder to place the abbreviation in, retrieved using C{engine.get_folder()} @param description: description for the phrase @param contents: the expansion text """ self.monitor.suspend() p = model.Phrase(description, contents) folder.add_item(p) p.persist() self.monitor.unsuspend() self.configManager.config_altered(False) def create_abbreviation(self, folder, description, abbr, contents): """ Create a text abbreviation Usage: C{engine.create_abbreviation(folder, description, abbr, contents)} When the given abbreviation is typed, it will be replaced with the given text. @param folder: folder to place the abbreviation in, retrieved using C{engine.get_folder()} @param description: description for the phrase @param abbr: the abbreviation that will trigger the expansion @param contents: the expansion text @raise Exception: if the specified abbreviation is not unique """ if not self.configManager.check_abbreviation_unique(abbr, None, None): raise Exception("The specified abbreviation is already in use") self.monitor.suspend() p = model.Phrase(description, contents) p.modes.append(model.TriggerMode.ABBREVIATION) p.abbreviations = [abbr] folder.add_item(p) p.persist() self.monitor.unsuspend() self.configManager.config_altered(False) def create_hotkey(self, folder, description, modifiers, key, contents): """ Create a text hotkey Usage: C{engine.create_hotkey(folder, description, modifiers, key, contents)} When the given hotkey is pressed, it will be replaced with the given text. Modifiers must be given as a list of strings, with the following values permitted: The key must be an unshifted character (i.e. lowercase) @param folder: folder to place the abbreviation in, retrieved using C{engine.get_folder()} @param description: description for the phrase @param modifiers: modifiers to use with the hotkey (as a list) @param key: the hotkey @param contents: the expansion text @raise Exception: if the specified hotkey is not unique """ modifiers.sort() if not self.configManager.check_hotkey_unique(modifiers, key, None, None): raise Exception("The specified hotkey and modifier combination is already in use") self.monitor.suspend() p = model.Phrase(description, contents) p.modes.append(model.TriggerMode.HOTKEY) p.set_hotkey(modifiers, key) folder.add_item(p) p.persist() self.monitor.unsuspend() self.configManager.config_altered(False) def run_script(self, description): """ Run an existing script using its description to look it up Usage: C{engine.run_script(description)} @param description: description of the script to run @raise Exception: if the specified script does not exist """ targetScript = None for item in self.configManager.allItems: if item.description == description and isinstance(item, model.Script): targetScript = item if targetScript is not None: self.runner.run_subscript(targetScript) else: raise Exception("No script with description '%s' found" % description) def run_script_from_macro(self, args): """ Used internally by AutoKey for phrase macros """ self.__macroArgs = args["args"].split(',') try: self.run_script(args["name"]) except Exception, e: self.set_return_value("{ERROR: %s}" % str(e)) def get_macro_arguments(self): """ Get the arguments supplied to the current script via its macro Usage: C{engine.get_macro_arguments()} @return: the arguments @rtype: C{list(str())} """ return self.__macroArgs def set_return_value(self, val): """ Store a return value to be used by a phrase macro Usage: C{engine.set_return_value(val)} @param val: value to be stored """ self.__returnValue = val def get_return_value(self): """ Used internally by AutoKey for phrase macros """ ret = self.__returnValue self.__returnValue = '' return ret autokey-0.90.4/src/lib/qtapp.py0000664000175000017500000002720011744657710015342 0ustar chrischris#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import common common.USING_QT = True import sys, traceback, os.path, signal, logging, logging.handlers, subprocess, Queue, time, dbus, dbus.mainloop.qt from PyKDE4.kdecore import KCmdLineArgs, KCmdLineOptions, KAboutData, ki18n, i18n from PyKDE4.kdeui import KMessageBox, KApplication from PyQt4.QtCore import SIGNAL, Qt, QObject, QEvent from PyQt4.QtGui import QCursor import service, monitor from qtui.notifier import Notifier from qtui.popupmenu import PopupMenu from qtui.configwindow import ConfigWindow from configmanager import * from common import * PROGRAM_NAME = ki18n("AutoKey") DESCRIPTION = ki18n("Desktop automation utility") LICENSE = KAboutData.License_GPL_V3 COPYRIGHT = ki18n("(c) 2009-2012 Chris Dekter") TEXT = ki18n("") class Application: """ Main application class; starting and stopping of the application is controlled from here, together with some interactions from the tray icon. """ def __init__(self): aboutData = KAboutData(APP_NAME, CATALOG, PROGRAM_NAME, VERSION, DESCRIPTION, LICENSE, COPYRIGHT, TEXT, HOMEPAGE, BUG_EMAIL) aboutData.addAuthor(ki18n("Chris Dekter"), ki18n("Developer"), "cdekter@gmail.com", "") aboutData.addAuthor(ki18n("Sam Peterson"), ki18n("Original developer"), "peabodyenator@gmail.com", "") aboutData.setProgramIconName(common.ICON_FILE) self.aboutData = aboutData KCmdLineArgs.init(sys.argv, aboutData) options = KCmdLineOptions() options.add("l").add("verbose", ki18n("Enable verbose logging")) options.add("c").add("configure", ki18n("Show the configuration window on startup")) KCmdLineArgs.addCmdLineOptions(options) args = KCmdLineArgs.parsedArgs() self.app = KApplication() try: # Create configuration directory if not os.path.exists(CONFIG_DIR): os.makedirs(CONFIG_DIR) # Initialise logger rootLogger = logging.getLogger() rootLogger.setLevel(logging.DEBUG) if args.isSet("verbose"): handler = logging.StreamHandler(sys.stdout) else: handler = logging.handlers.RotatingFileHandler(LOG_FILE, maxBytes=MAX_LOG_SIZE, backupCount=MAX_LOG_COUNT) handler.setLevel(logging.INFO) handler.setFormatter(logging.Formatter(LOG_FORMAT)) rootLogger.addHandler(handler) if self.__verifyNotRunning(): self.__createLockFile() self.initialise(args.isSet("configure")) except Exception, e: self.show_error_dialog(i18n("Fatal error starting AutoKey.\n") + str(e)) logging.exception("Fatal error starting AutoKey: " + str(e)) sys.exit(1) def __createLockFile(self): f = open(LOCK_FILE, 'w') f.write(str(os.getpid())) f.close() def __verifyNotRunning(self): if os.path.exists(LOCK_FILE): f = open(LOCK_FILE, 'r') pid = f.read() f.close() # Check that the found PID is running and is autokey p = subprocess.Popen(["ps", "-p", pid, "-o", "command"], stdout=subprocess.PIPE) p.wait() output = p.stdout.read() if "autokey" in output: logging.debug("AutoKey is already running as pid %s", pid) bus = dbus.SessionBus() try: dbusService = bus.get_object("org.autokey.Service", "/AppService") dbusService.show_configure(dbus_interface = "org.autokey.Service") sys.exit(0) except dbus.DBusException, e: logging.exception("Error communicating with Dbus service") self.show_error_dialog(i18n("AutoKey is already running as pid %1 but is not responding", pid), str(e)) sys.exit(1) return True def main(self): self.app.exec_() def initialise(self, configure): logging.info("Initialising application") self.monitor = monitor.FileMonitor(self) self.configManager = get_config_manager(self) self.service = service.Service(self) self.serviceDisabled = False # Initialise user code dir if self.configManager.userCodeDir is not None: sys.path.append(self.configManager.userCodeDir) try: self.service.start() except Exception, e: logging.exception("Error starting interface: " + str(e)) self.serviceDisabled = True self.show_error_dialog(i18n("Error starting interface. Keyboard monitoring will be disabled.\n" + "Check your system/configuration."), str(e)) self.notifier = Notifier(self) self.configWindow = None self.monitor.start() dbus.mainloop.qt.DBusQtMainLoop(set_as_default=True) self.dbusService = common.AppService(self) if ConfigManager.SETTINGS[IS_FIRST_RUN] or configure: ConfigManager.SETTINGS[IS_FIRST_RUN] = False self.show_configure() self.handler = CallbackEventHandler() kbChangeFilter = KeyboardChangeFilter(self.service.mediator.interface) self.app.installEventFilter(kbChangeFilter) def init_global_hotkeys(self, configManager): logging.info("Initialise global hotkeys") configManager.toggleServiceHotkey.set_closure(self.toggle_service) configManager.configHotkey.set_closure(self.show_configure_async) def config_altered(self, persistGlobal): self.configManager.config_altered(persistGlobal) self.notifier.build_menu() def hotkey_created(self, item): logging.debug("Created hotkey: %r %s", item.modifiers, item.hotKey) self.service.mediator.interface.grab_hotkey(item) def hotkey_removed(self, item): logging.debug("Removed hotkey: %r %s", item.modifiers, item.hotKey) self.service.mediator.interface.ungrab_hotkey(item) def path_created_or_modified(self, path): time.sleep(0.5) changed = self.configManager.path_created_or_modified(path) if changed and self.configWindow is not None: self.configWindow.config_modified() def path_removed(self, path): time.sleep(0.5) changed = self.configManager.path_removed(path) if changed and self.configWindow is not None: self.configWindow.config_modified() def unpause_service(self): """ Unpause the expansion service (start responding to keyboard and mouse events). """ self.service.unpause() self.notifier.update_tool_tip() def pause_service(self): """ Pause the expansion service (stop responding to keyboard and mouse events). """ self.service.pause() self.notifier.update_tool_tip() def toggle_service(self): """ Convenience method for toggling the expansion service on or off. """ if self.service.is_running(): self.pause_service() else: self.unpause_service() def shutdown(self): """ Shut down the entire application. """ logging.info("Shutting down") self.app.closeAllWindows() self.notifier.hide_icon() self.service.shutdown() self.monitor.stop() self.app.quit() os.remove(LOCK_FILE) logging.debug("All shutdown tasks complete... quitting") def notify_error(self, message): """ Show an error notification popup. @param message: Message to show in the popup """ self.exec_in_main(self.notifier.notify_error, message) def update_notifier_visibility(self): self.notifier.update_visible_status() def show_configure(self): """ Show the configuration window, or deiconify (un-minimise) it if it's already open. """ logging.info("Displaying configuration window") try: self.configWindow.showNormal() self.configWindow.activateWindow() except: self.configWindow = ConfigWindow(self) self.configWindow.show() def show_configure_async(self): self.exec_in_main(self.show_configure) def show_error_dialog(self, message, details=None): """ Convenience method for showing an error dialog. """ if details is None: KMessageBox.error(None, message) else: KMessageBox.detailedError(None, message, details) def show_script_error(self): """ Show the last script error (if any) """ if self.service.scriptRunner.error != '': KMessageBox.information(None, self.service.scriptRunner.error, i18n("View Script Error Details")) self.service.scriptRunner.error = '' else: KMessageBox.information(None, i18n("No error information available"), i18n("View Script Error Details")) def show_popup_menu(self, folders=[], items=[], onDesktop=True, title=None): self.exec_in_main(self.__createMenu, folders, items, onDesktop, title) def hide_menu(self): self.exec_in_main(self.menu.hide) def __createMenu(self, folders, items, onDesktop, title): self.menu = PopupMenu(self.service, folders, items, onDesktop, title) self.menu.popup(QCursor.pos()) self.menu.setFocus() def exec_in_main(self, callback, *args): self.handler.postEventWithCallback(callback, *args) class CallbackEventHandler(QObject): def __init__(self): QObject.__init__(self) self.queue = Queue.Queue() def customEvent(self, event): while True: try: callback, args = self.queue.get_nowait() except Queue.Empty: break try: callback(*args) except Exception: logging.warn("callback event failed: %r %r", callback, args, exc_info=True) def postEventWithCallback(self, callback, *args): self.queue.put((callback, args)) app = KApplication.kApplication() app.postEvent(self, QEvent(QEvent.User)) class KeyboardChangeFilter(QObject): def __init__(self, interface): QObject.__init__(self) self.interface = interface def eventFilter(self, obj, event): if event.type() == QEvent.KeyboardLayoutChange: self.interface.on_keys_changed() return QObject.eventFilter(obj, event) autokey-0.90.4/src/lib/gtkapp.py0000664000175000017500000002434411747641110015477 0ustar chrischris# -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import common common.USING_QT = False import sys, traceback, os.path, signal, logging, logging.handlers, subprocess, optparse, time import gettext, dbus, dbus.service, dbus.mainloop.glib from gi.repository import Gtk, Gdk, GObject, GLib gettext.install("autokey") import service, monitor from gtkui.notifier import get_notifier from gtkui.popupmenu import PopupMenu from gtkui.configwindow import ConfigWindow from configmanager import * from common import * PROGRAM_NAME = _("AutoKey") DESCRIPTION = _("Desktop automation utility") COPYRIGHT = _("(c) 2008-2011 Chris Dekter") class Application: """ Main application class; starting and stopping of the application is controlled from here, together with some interactions from the tray icon. """ def __init__(self): GLib.threads_init() Gdk.threads_init() p = optparse.OptionParser() p.add_option("-l", "--verbose", help="Enable verbose logging", action="store_true", default=False) p.add_option("-c", "--configure", help="Show the configuration window on startup", action="store_true", default=False) options, args = p.parse_args() try: # Create configuration directory if not os.path.exists(CONFIG_DIR): os.makedirs(CONFIG_DIR) # Initialise logger rootLogger = logging.getLogger() if options.verbose: rootLogger.setLevel(logging.DEBUG) handler = logging.StreamHandler(sys.stdout) else: rootLogger.setLevel(logging.INFO) handler = logging.handlers.RotatingFileHandler(LOG_FILE, maxBytes=MAX_LOG_SIZE, backupCount=MAX_LOG_COUNT) handler.setFormatter(logging.Formatter(LOG_FORMAT)) rootLogger.addHandler(handler) if self.__verifyNotRunning(): self.__createLockFile() self.initialise(options.configure) except Exception, e: self.show_error_dialog(_("Fatal error starting AutoKey.\n") + str(e)) logging.exception("Fatal error starting AutoKey: " + str(e)) sys.exit(1) def __createLockFile(self): f = open(LOCK_FILE, 'w') f.write(str(os.getpid())) f.close() def __verifyNotRunning(self): if os.path.exists(LOCK_FILE): f = open(LOCK_FILE, 'r') pid = f.read() f.close() # Check that the found PID is running and is autokey p = subprocess.Popen(["ps", "-p", pid, "-o", "command"], stdout=subprocess.PIPE) p.wait() output = p.stdout.read() if "autokey" in output: logging.debug("AutoKey is already running as pid %s", pid) bus = dbus.SessionBus() try: dbusService = bus.get_object("org.autokey.Service", "/AppService") dbusService.show_configure(dbus_interface = "org.autokey.Service") sys.exit(0) except dbus.DBusException, e: logging.exception("Error communicating with Dbus service") self.show_error_dialog(_("AutoKey is already running as pid %s but is not responding") % pid, str(e)) sys.exit(1) return True def main(self): Gtk.main() def initialise(self, configure): logging.info("Initialising application") self.monitor = monitor.FileMonitor(self) self.configManager = get_config_manager(self) self.service = service.Service(self) self.serviceDisabled = False # Initialise user code dir if self.configManager.userCodeDir is not None: sys.path.append(self.configManager.userCodeDir) try: self.service.start() except Exception, e: logging.exception("Error starting interface: " + str(e)) self.serviceDisabled = True self.show_error_dialog(_("Error starting interface. Keyboard monitoring will be disabled.\n" + "Check your system/configuration."), str(e)) self.notifier = get_notifier(self) self.configWindow = None self.monitor.start() dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) self.dbusService = common.AppService(self) if configure: self.show_configure() def init_global_hotkeys(self, configManager): logging.info("Initialise global hotkeys") configManager.toggleServiceHotkey.set_closure(self.toggle_service) configManager.configHotkey.set_closure(self.show_configure_async) def config_altered(self, persistGlobal): self.configManager.config_altered(persistGlobal) self.notifier.rebuild_menu() def hotkey_created(self, item): logging.debug("Created hotkey: %r %s", item.modifiers, item.hotKey) self.service.mediator.interface.grab_hotkey(item) def hotkey_removed(self, item): logging.debug("Removed hotkey: %r %s", item.modifiers, item.hotKey) self.service.mediator.interface.ungrab_hotkey(item) def path_created_or_modified(self, path): time.sleep(0.5) changed = self.configManager.path_created_or_modified(path) if changed and self.configWindow is not None: self.configWindow.config_modified() def path_removed(self, path): time.sleep(0.5) changed = self.configManager.path_removed(path) if changed and self.configWindow is not None: self.configWindow.config_modified() def unpause_service(self): """ Unpause the expansion service (start responding to keyboard and mouse events). """ self.service.unpause() self.notifier.update_tool_tip() def pause_service(self): """ Pause the expansion service (stop responding to keyboard and mouse events). """ self.service.pause() self.notifier.update_tool_tip() def toggle_service(self): """ Convenience method for toggling the expansion service on or off. """ if self.service.is_running(): self.pause_service() else: self.unpause_service() def shutdown(self): """ Shut down the entire application. """ if self.configWindow is not None: if self.configWindow.promptToSave(): return self.configWindow.hide() self.notifier.hide_icon() t = threading.Thread(target=self.__completeShutdown) t.start() def __completeShutdown(self): logging.info("Shutting down") self.service.shutdown() self.monitor.stop() Gdk.threads_enter() Gtk.main_quit() Gdk.threads_leave() os.remove(LOCK_FILE) logging.debug("All shutdown tasks complete... quitting") def notify_error(self, message): """ Show an error notification popup. @param message: Message to show in the popup """ self.notifier.notify_error(message) def update_notifier_visibility(self): self.notifier.update_visible_status() def show_configure(self): """ Show the configuration window, or deiconify (un-minimise) it if it's already open. """ logging.info("Displaying configuration window") if self.configWindow is None: self.configWindow = ConfigWindow(self) self.configWindow.show() else: self.configWindow.deiconify() def show_configure_async(self): Gdk.threads_enter() self.show_configure() Gdk.threads_leave() def main(self): logging.info("Entering main()") Gdk.threads_enter() Gtk.main() Gdk.threads_leave() def show_error_dialog(self, message, details=None): """ Convenience method for showing an error dialog. """ dlg = Gtk.MessageDialog(type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK, message_format=message) if details is not None: dlg.format_secondary_text(details) dlg.run() dlg.destroy() def show_script_error(self, parent): """ Show the last script error (if any) """ if self.service.scriptRunner.error != '': dlg = Gtk.MessageDialog(type=Gtk.MessageType.INFO, buttons=Gtk.ButtonsType.OK, message_format=self.service.scriptRunner.error) self.service.scriptRunner.error = '' else: dlg = Gtk.MessageDialog(type=Gtk.MessageType.INFO, buttons=Gtk.ButtonsType.OK, message_format=_("No error information available")) dlg.set_title(_("View script error")) dlg.set_transient_for(parent) dlg.run() dlg.destroy() def show_popup_menu(self, folders=[], items=[], onDesktop=True, title=None): self.menu = PopupMenu(self.service, folders, items, onDesktop, title) self.menu.show_on_desktop() def hide_menu(self): self.menu.remove_from_desktop() autokey-0.90.4/src/lib/monitor.py0000664000175000017500000001045211661345307015677 0ustar chrischris#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import threading, logging, os.path, time from pyinotify import WatchManager, Notifier, EventsCodes, ProcessEvent _logger = logging.getLogger("inotify") m = EventsCodes.OP_FLAGS MASK = m["IN_CREATE"]|m["IN_MODIFY"]|m["IN_DELETE"]|m["IN_MOVED_TO"]|m["IN_MOVED_FROM"] class Processor(ProcessEvent): def __init__(self, monitor, listener): ProcessEvent.__init__(self) self.listener = listener self.monitor = monitor def __getEventPath(self, event): if event.name != '': path = os.path.join(event.path, event.name) else: path = event.path _logger.debug("Reporting %s event at %s", event.maskname, path) return path def process_IN_MOVED_TO(self, event): path = self.__getEventPath(event) if not self.monitor.is_suspended(): self.listener.path_created_or_modified(path) def process_IN_CREATE(self, event): path = self.__getEventPath(event) if not self.monitor.is_suspended(): self.listener.path_created_or_modified(path) def process_IN_MODIFY(self, event): path = self.__getEventPath(event) if not self.monitor.is_suspended(): self.listener.path_created_or_modified(path) def process_IN_DELETE(self, event): path = self.__getEventPath(event) if not self.monitor.is_suspended(): self.listener.path_removed(path) def process_IN_MOVED_FROM(self, event): path = self.__getEventPath(event) if not self.monitor.is_suspended(): self.listener.path_removed(path) class FileMonitor(threading.Thread): def __init__(self, listener): threading.Thread.__init__(self) self.__p = Processor(self, listener) self.manager = WatchManager() self.notifier = Notifier(self.manager, self.__p) self.event = threading.Event() self.setDaemon(True) self.watches = [] #self.suspended = [] self.__isSuspended = False def suspend(self): self.__isSuspended = True def unsuspend(self): t = threading.Thread(target=self.__unsuspend) t.start() def __unsuspend(self): time.sleep(1.5) self.__isSuspended = False for watch in self.watches: if not os.path.exists(watch): _logger.debug("Removed stale watch on %s", watch) self.watches.remove(watch) def is_suspended(self): return self.__isSuspended def has_watch(self, path): return path in self.watches def add_watch(self, path): _logger.debug("Adding watch for %s", path) self.manager.add_watch(path, MASK, self.__p) self.watches.append(path) def remove_watch(self, path): _logger.debug("Removing watch for %s", path) wd = self.manager.get_wd(path) self.manager.rm_watch(wd, True) self.watches.remove(path) for i in range(len(self.watches)): try: if self.watches[i].startswith(path): self.watches.remove(self.watches[i]) except IndexError: break def run(self): while not self.event.isSet(): self.notifier.process_events() if self.notifier.check_events(1000): self.notifier.read_events() _logger.info("Shutting down file monitor") self.notifier.stop() def stop(self): self.event.set() self.join() autokey-0.90.4/src/lib/common.py0000664000175000017500000000505011754440521015473 0ustar chrischris#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os.path, dbus.service CONFIG_DIR = os.path.expanduser("~/.config/autokey") LOCK_FILE = CONFIG_DIR + "/autokey.pid" LOG_FILE = CONFIG_DIR + "/autokey.log" MAX_LOG_SIZE = 5 * 1024 * 1024 # 5 megabytes MAX_LOG_COUNT = 3 LOG_FORMAT = "%(asctime)s %(levelname)s - %(name)s - %(message)s" APP_NAME = "autokey" CATALOG = "" VERSION = "0.90.4" HOMEPAGE = "http://autokey.googlecode.com/" BUG_EMAIL = "cdekter@gmail.com" FAQ_URL = "http://code.google.com/p/autokey/wiki/FAQ" API_URL = "http://autokey.googlecode.com/svn/trunk/doc/scripting/index.html" HELP_URL = "http://code.google.com/p/autokey/w/list" DONATE_URL = "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=L333CPRZ6J8JC" BUG_URL = "http://code.google.com/p/autokey/issues/entry" ICON_FILE = "autokey" ICON_FILE_NOTIFICATION = "autokey-status" ICON_FILE_NOTIFICATION_DARK = "autokey-status-dark" ICON_FILE_NOTIFICATION_ERROR = "autokey-status-error" USING_QT = False class AppService(dbus.service.Object): def __init__(self, app): busName = dbus.service.BusName('org.autokey.Service', bus=dbus.SessionBus()) dbus.service.Object.__init__(self, busName, "/AppService") self.app = app @dbus.service.method(dbus_interface='org.autokey.Service', in_signature='', out_signature='') def show_configure(self): self.app.show_configure() @dbus.service.method(dbus_interface='org.autokey.Service', in_signature='s', out_signature='') def run_script(self, name): self.app.service.run_script(name) @dbus.service.method(dbus_interface='org.autokey.Service', in_signature='s', out_signature='') def run_phrase(self, name): self.app.service.run_phrase(name) @dbus.service.method(dbus_interface='org.autokey.Service', in_signature='s', out_signature='') def run_folder(self, name): self.app.service.run_folder(name) autokey-0.90.4/src/lib/iomediator.py0000664000175000017500000004054711754436675016370 0ustar chrischris# -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . X_RECORD_INTERFACE = "XRecord" ATSPI_INTERFACE = "AT-SPI" INTERFACES = [X_RECORD_INTERFACE, ATSPI_INTERFACE] CURRENT_INTERFACE = None # Key codes enumeration class Key: LEFT = "" RIGHT = "" UP = "" DOWN = "" BACKSPACE = "" TAB = "" ENTER = "" SCROLL_LOCK = "" PRINT_SCREEN = "" PAUSE = "" MENU = "" # Modifier keys CONTROL = "" ALT = "" ALT_GR = "" SHIFT = "" SUPER = "" HYPER = "" CAPSLOCK = "" NUMLOCK = "" META = "" F1 = "" F2 = "" F3 = "" F4 = "" F5 = "" F6 = "" F7 = "" F8 = "" F9 = "" F10 = "" F11 = "" F12 = "" # Other ESCAPE = "" INSERT = "" DELETE = "" HOME = "" END = "" PAGE_UP = "" PAGE_DOWN = "" # Numpad NP_INSERT = "" NP_DELETE = "" NP_HOME = "" NP_END = "" NP_PAGE_UP = "" NP_PAGE_DOWN = "" NP_LEFT = "" NP_RIGHT = "" NP_UP = "" NP_DOWN = "" NP_DIVIDE = "" NP_MULTIPLY = "" NP_ADD = "" NP_SUBTRACT = "" NP_5 = "" @classmethod def is_key(klass, keyString): # Key strings must be treated as case insensitive - always convert to lowercase # before doing any comparisons return keyString.lower() in klass.__dict__.values() or keyString.startswith("\+{0,1})", re.UNICODE) KEY_SPLIT_RE = re.compile("(<[^<>]+>\+?)", re.UNICODE) SEND_LOCK = threading.Lock() from interface import * from configmanager import * class IoMediator(threading.Thread): """ The IoMediator is responsible for tracking the state of modifier keys and interfacing with the various Interface classes to obtain the correct characters to pass to the expansion service. This class must not store or maintain any configuration details. """ # List of targets interested in receiving keypress, hotkey and mouse events listeners = [] def __init__(self, service): threading.Thread.__init__(self, name="KeypressHandler-thread") self.queue = Queue.Queue() self.listeners.append(service) self.interfaceType = ConfigManager.SETTINGS[INTERFACE_TYPE] # Modifier tracking self.modifiers = { Key.CONTROL : False, Key.ALT : False, Key.ALT_GR: False, Key.SHIFT : False, Key.SUPER : False, Key.HYPER : False, Key.META : False, Key.CAPSLOCK : False, Key.NUMLOCK : False } if self.interfaceType == X_RECORD_INTERFACE: self.interface = XRecordInterface(self, service.app) elif self.interfaceType == X_EVDEV_INTERFACE: self.interface = EvDevInterface(self, service.app) else: self.interface = AtSpiInterface(self, service.app) global CURRENT_INTERFACE CURRENT_INTERFACE = self.interface def shutdown(self): self.interface.cancel() self.queue.put_nowait((None, None, None)) self.join() # Callback methods for Interfaces ---- def set_modifier_state(self, modifier, state): _logger.debug("Set modifier %s to %r", modifier, state) self.modifiers[modifier] = state def handle_modifier_down(self, modifier): """ Updates the state of the given modifier key to 'pressed' """ _logger.debug("%s pressed", modifier) if modifier in (Key.CAPSLOCK, Key.NUMLOCK): if self.modifiers[modifier]: self.modifiers[modifier] = False else: self.modifiers[modifier] = True else: self.modifiers[modifier] = True def handle_modifier_up(self, modifier): """ Updates the state of the given modifier key to 'released'. """ _logger.debug("%s released", modifier) # Caps and num lock are handled on key down only if not modifier in (Key.CAPSLOCK, Key.NUMLOCK): self.modifiers[modifier] = False def handle_keypress(self, keyCode, windowName, windowClass): """ Looks up the character for the given key code, applying any modifiers currently in effect, and passes it to the expansion service. """ self.queue.put_nowait((keyCode, windowName, windowClass)) def run(self): while True: keyCode, windowName, windowClass = self.queue.get() if keyCode is None and windowName is None: break numLock = self.modifiers[Key.NUMLOCK] modifiers = self.__getModifiersOn() shifted = self.modifiers[Key.CAPSLOCK] ^ self.modifiers[Key.SHIFT] key = self.interface.lookup_string(keyCode, shifted, numLock, self.modifiers[Key.ALT_GR]) rawKey = self.interface.lookup_string(keyCode, False, False, False) for target in self.listeners: target.handle_keypress(rawKey, modifiers, key, windowName, windowClass) self.queue.task_done() def handle_mouse_click(self, rootX, rootY, relX, relY, button, windowInfo): for target in self.listeners: target.handle_mouseclick(rootX, rootY, relX, relY, button, windowInfo) # Methods for expansion service ---- def send_string(self, string): """ Sends the given string for output. """ if len(string) == 0: return k = Key() string = string.replace('\n', "") string = string.replace('\t', "") _logger.debug("Send via event interface") self.__clearModifiers() modifiers = [] for section in KEY_SPLIT_RE.split(string): if len(section) > 0: if k.is_key(section[:-1]) and section[-1] == '+' and section[:-1] in MODIFIERS: # Section is a modifier application (modifier followed by '+') modifiers.append(section[:-1]) else: if len(modifiers) > 0: # Modifiers ready for application - send modified key if k.is_key(section): self.interface.send_modified_key(section, modifiers) modifiers = [] else: self.interface.send_modified_key(section[0], modifiers) if len(section) > 1: self.interface.send_string(section[1:]) modifiers = [] else: # Normal string/key operation if k.is_key(section): self.interface.send_key(section) else: self.interface.send_string(section) self.__reapplyModifiers() def paste_string(self, string, pasteCommand): if len(string) > 0: _logger.debug("Send via clipboard") self.interface.send_string_clipboard(string, pasteCommand) def remove_string(self, string): backspaces = -1 # Start from -1 to discount the backspace already pressed by the user k = Key() for section in KEY_SPLIT_RE.split(string): if k.is_key(section): backspaces += 1 else: backspaces += len(section) self.send_backspace(backspaces) def send_key(self, keyName): keyName = keyName.replace('\n', "") self.interface.send_key(keyName) def press_key(self, keyName): keyName = keyName.replace('\n', "") self.interface.fake_keydown(keyName) def release_key(self, keyName): keyName = keyName.replace('\n', "") self.interface.fake_keyup(keyName) def fake_keypress(self, keyName): keyName = keyName.replace('\n', "") self.interface.fake_keypress(keyName) def send_left(self, count): """ Sends the given number of left key presses. """ for i in range(count): self.interface.send_key(Key.LEFT) def send_right(self, count): for i in range(count): self.interface.send_key(Key.RIGHT) def send_up(self, count): """ Sends the given number of up key presses. """ for i in range(count): self.interface.send_key(Key.UP) def send_backspace(self, count): """ Sends the given number of backspace key presses. """ for i in range(count): self.interface.send_key(Key.BACKSPACE) def send_mouse_click(self, x, y, button, relative): self.interface.send_mouse_click(x, y, button, relative) def send_mouse_click_relative(self, x, y, button): self.interface.send_mouse_click_relative(x, y, button) def flush(self): self.interface.flush() # Utility methods ---- def __clearModifiers(self): self.releasedModifiers = [] for modifier in self.modifiers.keys(): if self.modifiers[modifier] and not modifier in (Key.CAPSLOCK, Key.NUMLOCK): self.releasedModifiers.append(modifier) self.interface.release_key(modifier) def __reapplyModifiers(self): for modifier in self.releasedModifiers: self.interface.press_key(modifier) def __getModifiersOn(self): modifiers = [] for modifier in HELD_MODIFIERS: if self.modifiers[modifier]: modifiers.append(modifier) modifiers.sort() return modifiers class Waiter: """ Waits for a specified event to occur """ def __init__(self, rawKey, modifiers, button, timeOut): IoMediator.listeners.append(self) self.rawKey = rawKey self.modifiers = modifiers self.button = button self.event = threading.Event() self.timeOut = timeOut if modifiers is not None: self.modifiers.sort() def wait(self): self.event.wait(self.timeOut) def handle_keypress(self, rawKey, modifiers, key, *args): if rawKey == self.rawKey and modifiers == self.modifiers: IoMediator.listeners.remove(self) self.event.set() def handle_mouseclick(self, rootX, rootY, relX, relY, button, windowInfo): if button == self.button: self.event.set() class KeyGrabber: """ Keygrabber used by the hotkey settings dialog to grab the key pressed """ def __init__(self, parent): self.targetParent = parent def start(self): # In QT version, sometimes the mouseclick event arrives before we finish initialising # sleep slightly to prevent this time.sleep(0.1) IoMediator.listeners.append(self) CURRENT_INTERFACE.grab_keyboard() def handle_keypress(self, rawKey, modifiers, key, *args): if not rawKey in MODIFIERS: IoMediator.listeners.remove(self) self.targetParent.set_key(rawKey, modifiers) CURRENT_INTERFACE.ungrab_keyboard() def handle_mouseclick(self, rootX, rootY, relX, relY, button, windowInfo): IoMediator.listeners.remove(self) CURRENT_INTERFACE.ungrab_keyboard() self.targetParent.cancel_grab() class Recorder(KeyGrabber): """ Recorder used by the record macro functionality """ def __init__(self, parent): KeyGrabber.__init__(self, parent) self.insideKeys = False def start(self, delay): time.sleep(0.1) IoMediator.listeners.append(self) self.targetParent.start_record() self.startTime = time.time() self.delay = delay self.delayFinished = False def start_withgrab(self): time.sleep(0.1) IoMediator.listeners.append(self) self.targetParent.start_record() self.startTime = time.time() self.delay = 0 self.delayFinished = True CURRENT_INTERFACE.grab_keyboard() def stop(self): if self in IoMediator.listeners: IoMediator.listeners.remove(self) if self.insideKeys: self.targetParent.end_key_sequence() self.insideKeys = False def stop_withgrab(self): CURRENT_INTERFACE.ungrab_keyboard() if self in IoMediator.listeners: IoMediator.listeners.remove(self) if self.insideKeys: self.targetParent.end_key_sequence() self.insideKeys = False def set_record_keyboard(self, doIt): self.recordKeyboard = doIt def set_record_mouse(self, doIt): self.recordMouse = doIt def __delayPassed(self): if not self.delayFinished: now = time.time() delta = datetime.datetime.utcfromtimestamp(now - self.startTime) self.delayFinished = (delta.second > self.delay) return self.delayFinished def handle_keypress(self, rawKey, modifiers, key, *args): if self.recordKeyboard and self.__delayPassed(): if not self.insideKeys: self.insideKeys = True self.targetParent.start_key_sequence() modifierCount = len(modifiers) if modifierCount > 1 or (modifierCount == 1 and Key.SHIFT not in modifiers) or \ (Key.SHIFT in modifiers and len(rawKey) > 1): self.targetParent.append_hotkey(rawKey, modifiers) elif not key in MODIFIERS: self.targetParent.append_key(key) def handle_mouseclick(self, rootX, rootY, relX, relY, button, windowInfo): if self.recordMouse and self.__delayPassed(): if self.insideKeys: self.insideKeys = False self.targetParent.end_key_sequence() self.targetParent.append_mouseclick(relX, relY, button, windowInfo[0]) class WindowGrabber: def __init__(self, dialog): self.dialog = dialog def start(self): time.sleep(0.1) IoMediator.listeners.append(self) def handle_keypress(self, rawKey, modifiers, key, *args): pass def handle_mouseclick(self, rootX, rootY, relX, relY, button, windowInfo): IoMediator.listeners.remove(self) self.dialog.receive_window_info(windowInfo) autokey-0.90.4/src/lib/__init__.py0000664000175000017500000000000011120604061015714 0ustar chrischrisautokey-0.90.4/src/lib/configmanager.py0000664000175000017500000006734211744242342017017 0ustar chrischris# -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os, os.path, shutil, logging, pickle, glob, threading, subprocess import iomediator, interface, common, monitor try: import json l = json.load except: import simplejson as json _logger = logging.getLogger("config-manager") CONFIG_DIR = os.path.expanduser("~/.config/autokey") CONFIG_FILE = os.path.join(CONFIG_DIR, "autokey.json") CONFIG_DEFAULT_FOLDER = os.path.join(CONFIG_DIR, "data") CONFIG_FILE_BACKUP = CONFIG_FILE + '~' DEFAULT_ABBR_FOLDER = "Imported Abbreviations" RECENT_ENTRIES_FOLDER = "Recently Typed" IS_FIRST_RUN = "isFirstRun" SERVICE_RUNNING = "serviceRunning" MENU_TAKES_FOCUS = "menuTakesFocus" SHOW_TRAY_ICON = "showTrayIcon" SORT_BY_USAGE_COUNT = "sortByUsageCount" #DETECT_UNWANTED_ABBR = "detectUnwanted" PROMPT_TO_SAVE = "promptToSave" #PREDICTIVE_LENGTH = "predictiveLength" INPUT_SAVINGS = "inputSavings" ENABLE_QT4_WORKAROUND = "enableQT4Workaround" INTERFACE_TYPE = "interfaceType" UNDO_USING_BACKSPACE = "undoUsingBackspace" WINDOW_DEFAULT_SIZE = "windowDefaultSize" HPANE_POSITION = "hPanePosition" COLUMN_WIDTHS = "columnWidths" SHOW_TOOLBAR = "showToolbar" NOTIFICATION_ICON = "notificationIcon" WORKAROUND_APP_REGEX = "workAroundApps" SCRIPT_GLOBALS = "scriptGlobals" # TODO - Future functionality #TRACK_RECENT_ENTRY = "trackRecentEntry" #RECENT_ENTRY_COUNT = "recentEntryCount" #RECENT_ENTRY_MINLENGTH = "recentEntryMinLength" #RECENT_ENTRY_SUGGEST = "recentEntrySuggest" def get_config_manager(autoKeyApp, hadError=False): if not os.path.exists(CONFIG_DEFAULT_FOLDER): os.mkdir(CONFIG_DEFAULT_FOLDER) try: configManager = ConfigManager(autoKeyApp) except Exception, e: if hadError or not os.path.exists(CONFIG_FILE_BACKUP) or not os.path.exists(CONFIG_FILE): _logger.error("Error while loading configuration. Cannot recover.") raise _logger.exception("Error while loading configuration. Backup has been restored.") os.remove(CONFIG_FILE) shutil.copy2(CONFIG_FILE_BACKUP, CONFIG_FILE) return get_config_manager(autoKeyApp, True) _logger.debug("Global settings: %r", ConfigManager.SETTINGS) return configManager def save_config(configManager): _logger.info("Persisting configuration") configManager.app.monitor.suspend() # Back up configuration if it exists if os.path.exists(CONFIG_FILE): _logger.info("Backing up existing config file") shutil.copy2(CONFIG_FILE, CONFIG_FILE_BACKUP) try: outFile = open(CONFIG_FILE, "w") json.dump(configManager.get_serializable(), outFile, indent=4) _logger.info("Finished persisting configuration - no errors") except Exception, e: if os.path.exists(CONFIG_FILE_BACKUP): shutil.copy2(CONFIG_FILE_BACKUP, CONFIG_FILE) _logger.exception("Error while saving configuration. Backup has been restored (if found).") raise Exception("Error while saving configuration. Backup has been restored (if found).") finally: outFile.close() configManager.app.monitor.unsuspend() def apply_settings(settings): """ Allows new settings to be added without users having to lose all their configuration """ for key, value in settings.iteritems(): ConfigManager.SETTINGS[key] = value def convert_v07_to_v08(configData): oldVersion = configData["version"] os.rename(CONFIG_FILE, CONFIG_FILE + oldVersion) _logger.info("Converting v%s configuration data to v0.80.0", oldVersion) for folderData in configData["folders"]: _convertFolder(folderData, None) configData["folders"] = [] configData["version"] = common.VERSION configData["settings"][NOTIFICATION_ICON] = common.ICON_FILE_NOTIFICATION # Remove old backup file so we never retry the conversion if os.path.exists(CONFIG_FILE_BACKUP): os.remove(CONFIG_FILE_BACKUP) _logger.info("Conversion succeeded") def _convertFolder(folderData, parent): f = Folder("") f.inject_json_data(folderData) f.parent = parent f.persist() for subfolder in folderData["folders"]: _convertFolder(subfolder, f) for itemData in folderData["items"]: i = None if itemData["type"] == "script": i = Script("", "") i.code = itemData["code"] elif itemData["type"] == "phrase": i = Phrase("", "") i.phrase = itemData["phrase"] if i is not None: i.inject_json_data(itemData) i.parent = f i.persist() class ConfigManager: """ Contains all application configuration, and provides methods for updating and maintaining consistency of the configuration. """ """ Static member for global application settings. """ CLASS_VERSION = common.VERSION SETTINGS = { IS_FIRST_RUN : True, SERVICE_RUNNING : True, MENU_TAKES_FOCUS : False, SHOW_TRAY_ICON : True, SORT_BY_USAGE_COUNT : True, #DETECT_UNWANTED_ABBR : False, PROMPT_TO_SAVE: False, #PREDICTIVE_LENGTH : 5, ENABLE_QT4_WORKAROUND : False, INTERFACE_TYPE : iomediator.X_RECORD_INTERFACE, UNDO_USING_BACKSPACE : True, WINDOW_DEFAULT_SIZE : (600, 400), HPANE_POSITION : 150, COLUMN_WIDTHS : [150, 50, 100], SHOW_TOOLBAR : True, NOTIFICATION_ICON : common.ICON_FILE_NOTIFICATION, WORKAROUND_APP_REGEX : ".*VirtualBox.*|krdc.Krdc", # TODO - Future functionality #TRACK_RECENT_ENTRY : True, #RECENT_ENTRY_COUNT : 5, #RECENT_ENTRY_MINLENGTH : 10, #RECENT_ENTRY_SUGGEST : True SCRIPT_GLOBALS : {} } def __init__(self, app): """ Create initial default configuration """ self.VERSION = self.__class__.CLASS_VERSION self.lock = threading.Lock() self.app = app self.folders = [] self.userCodeDir = None self.configHotkey = GlobalHotkey() self.configHotkey.set_hotkey([""], "k") self.configHotkey.enabled = True self.toggleServiceHotkey = GlobalHotkey() self.toggleServiceHotkey.set_hotkey(["", ""], "k") self.toggleServiceHotkey.enabled = True app.init_global_hotkeys(self) self.load_global_config() self.app.monitor.add_watch(CONFIG_DEFAULT_FOLDER) self.app.monitor.add_watch(CONFIG_DIR) if self.folders: return # --- Code below here only executed if no persisted config data provided _logger.info("No configuration found - creating new one") myPhrases = Folder(u"My Phrases") myPhrases.set_hotkey([""], "") myPhrases.set_modes([TriggerMode.HOTKEY]) myPhrases.persist() f = Folder(u"Addresses") adr = Phrase(u"Home Address", u"22 Avenue Street\nBrisbane\nQLD\n4000") adr.set_modes([TriggerMode.ABBREVIATION]) adr.add_abbreviation(u"adr") f.add_item(adr) myPhrases.add_folder(f) f.persist() adr.persist() p = Phrase(u"First phrase", u"Test phrase number one!") p.set_modes([TriggerMode.PREDICTIVE]) p.set_window_titles(".* - gedit") myPhrases.add_item(p) myPhrases.add_item(Phrase(u"Second phrase", u"Test phrase number two!")) myPhrases.add_item(Phrase(u"Third phrase", u"Test phrase number three!")) self.folders.append(myPhrases) [p.persist() for p in myPhrases.items] sampleScripts = Folder(u"Sample Scripts") sampleScripts.persist() dte = Script("Insert Date", "") dte.code = """output = system.exec_command("date") keyboard.send_keys(output)""" sampleScripts.add_item(dte) lMenu = Script("List Menu", "") lMenu.code = """choices = ["something", "something else", "a third thing"] retCode, choice = dialog.list_menu(choices) if retCode == 0: keyboard.send_keys("You chose " + choice)""" sampleScripts.add_item(lMenu) sel = Script("Selection Test", "") sel.code = """text = clipboard.get_selection() keyboard.send_key("") keyboard.send_keys("The text %s was here previously" % text)""" sampleScripts.add_item(sel) abbrc = Script("Abbreviation from selection", "") abbrc.code = """import time time.sleep(0.25) contents = clipboard.get_selection() retCode, abbr = dialog.input_dialog("New Abbreviation", "Choose an abbreviation for the new phrase") if retCode == 0: if len(contents) > 20: title = contents[0:17] + "..." else: title = contents folder = engine.get_folder("My Phrases") engine.create_abbreviation(folder, title, abbr, contents)""" sampleScripts.add_item(abbrc) phrasec = Script("Phrase from selection", "") phrasec.code = """import time time.sleep(0.25) contents = clipboard.get_selection() if len(contents) > 20: title = contents[0:17] + "..." else: title = contents folder = engine.get_folder("My Phrases") engine.create_phrase(folder, title, contents)""" sampleScripts.add_item(phrasec) win = Script("Display window info", "") win.code = """# Displays the information of the next window to be left-clicked import time mouse.wait_for_click(1) time.sleep(0.2) winTitle = window.get_active_title() winClass = window.get_active_class() dialog.info_dialog("Window information", "Active window information:\\nTitle: '%s'\\nClass: '%s'" % (winTitle, winClass))""" win.showInTrayMenu = True sampleScripts.add_item(win) self.folders.append(sampleScripts) [s.persist() for s in sampleScripts.items] # TODO - future functionality self.recentEntries = [] self.config_altered(True) def get_serializable(self): extraFolders = [] for folder in self.folders: if not folder.path.startswith(CONFIG_DEFAULT_FOLDER): extraFolders.append(folder.path) d = { "version": self.VERSION, "userCodeDir": self.userCodeDir, "settings": ConfigManager.SETTINGS, "folders": extraFolders, "toggleServiceHotkey": self.toggleServiceHotkey.get_serializable(), "configHotkey": self.configHotkey.get_serializable() } return d def load_global_config(self): if os.path.exists(CONFIG_FILE): _logger.info("Loading config from existing file: " + CONFIG_FILE) with open(CONFIG_FILE, 'r') as pFile: data = json.load(pFile) version = data["version"] if version < "0.80.0": try: convert_v07_to_v08(data) self.config_altered(True) except Exception, e: _logger.exception("Problem occurred during conversion.") _logger.error("Existing config file has been saved as %s%s", CONFIG_FILE, version) raise self.VERSION = data["version"] self.userCodeDir = data["userCodeDir"] apply_settings(data["settings"]) self.workAroundApps = re.compile(self.SETTINGS[WORKAROUND_APP_REGEX]) for entryPath in glob.glob(CONFIG_DEFAULT_FOLDER + "/*"): if os.path.isdir(entryPath): _logger.debug("Loading folder at '%s'", entryPath) f = Folder("", path=entryPath) f.load(None) self.folders.append(f) for folderPath in data["folders"]: f = Folder("", path=folderPath) f.load() self.folders.append(f) self.toggleServiceHotkey.load_from_serialized(data["toggleServiceHotkey"]) self.configHotkey.load_from_serialized(data["configHotkey"]) if self.VERSION < self.CLASS_VERSION: self.upgrade() self.config_altered(False) _logger.info("Successfully loaded configuration") def __checkExisting(self, path): # Check if we already know about the path, and return object if found for item in self.allItems: if item.path == path: return item return None def __checkExistingFolder(self, path): for folder in self.allFolders: if folder.path == path: return folder return None def path_created_or_modified(self, path): directory, baseName = os.path.split(path) loaded = False if path == CONFIG_FILE: self.reload_global_config() elif directory != CONFIG_DIR: # ignore all other changes in top dir # --- handle directories added if os.path.isdir(path): f = Folder("", path=path) if directory == CONFIG_DEFAULT_FOLDER: self.folders.append(f) f.load() loaded = True else: folder = self.__checkExistingFolder(directory) if folder is not None: f.load(folder) folder.add_folder(f) loaded = True # -- handle txt or py files added or modified elif os.path.isfile(path): i = self.__checkExisting(path) isNew = False if i is None: isNew = True if baseName.endswith(".txt"): i = Phrase("", "", path=path) elif baseName.endswith(".py"): i = Script("", "", path=path) if i is not None: folder = self.__checkExistingFolder(directory) if folder is not None: i.load(folder) if isNew: folder.add_item(i) loaded = True # --- handle changes to folder settings if baseName == ".folder.json": folder = self.__checkExistingFolder(directory) if folder is not None: folder.load_from_serialized() loaded = True # --- handle changes to item settings if baseName.endswith(".json"): for item in self.allItems: if item.get_json_path() == path: item.load_from_serialized() loaded = True if not loaded: _logger.warn("No action taken for create/update event at %s", path) else: self.config_altered(False) return loaded def path_removed(self, path): directory, baseName = os.path.split(path) deleted = False if directory == CONFIG_DIR: # ignore all deletions in top dir return folder = self.__checkExistingFolder(path) item = self.__checkExisting(path) if folder is not None: if folder.parent is None: self.folders.remove(folder) else: folder.parent.remove_folder(folder) deleted = True elif item is not None: item.parent.remove_item(item) #item.remove_data() deleted = True if not deleted: _logger.warn("No action taken for delete event at %s", path) else: self.config_altered(False) return deleted def reload_global_config(self): _logger.info("Reloading global configuration") with open(CONFIG_FILE, 'r') as pFile: data = json.load(pFile) self.userCodeDir = data["userCodeDir"] apply_settings(data["settings"]) self.workAroundApps = re.compile(self.SETTINGS[WORKAROUND_APP_REGEX]) existingPaths = [] for folder in self.folders: if folder.parent is None and not folder.path.startswith(CONFIG_DEFAULT_FOLDER): existingPaths.append(folder.path) for folderPath in data["folders"]: if folderPath not in existingPaths: f = Folder("", path=folderPath) f.load() self.folders.append(f) self.toggleServiceHotkey.load_from_serialized(data["toggleServiceHotkey"]) self.configHotkey.load_from_serialized(data["configHotkey"]) self.config_altered(False) _logger.info("Successfully reloaded global configuration") def upgrade(self): _logger.info("Checking if upgrade is needed from version %s", self.VERSION) # Always reset interface type when upgrading self.SETTINGS[INTERFACE_TYPE] = iomediator.X_RECORD_INTERFACE _logger.info("Resetting interface type, new type: %s", self.SETTINGS[INTERFACE_TYPE]) if self.VERSION < '0.70.0': _logger.info("Doing upgrade to 0.70.0") for item in self.allItems: if isinstance(item, Phrase): item.sendMode = SendMode.KEYBOARD if self.VERSION < "0.82.3": self.SETTINGS[WORKAROUND_APP_REGEX] += "|krdc.Krdc" self.workAroundApps = re.compile(self.SETTINGS[WORKAROUND_APP_REGEX]) self.SETTINGS[SCRIPT_GLOBALS] = {} self.VERSION = common.VERSION self.config_altered(True) def config_altered(self, persistGlobal): """ Called when some element of configuration has been altered, to update the lists of phrases/folders. @param persist: save the global configuration at the end of the process """ _logger.info("Configuration changed - rebuilding in-memory structures") self.lock.acquire() # Rebuild root folder list #rootFolders = self.folders #self.folders = [] #for folder in rootFolders: # self.folders.append(folder) self.hotKeyFolders = [] self.hotKeys = [] self.abbreviations = [] self.allFolders = [] self.allItems = [] for folder in self.folders: if TriggerMode.HOTKEY in folder.modes: self.hotKeyFolders.append(folder) self.allFolders.append(folder) if not self.app.monitor.has_watch(folder.path): self.app.monitor.add_watch(folder.path) self.__processFolder(folder) self.globalHotkeys = [] self.globalHotkeys.append(self.configHotkey) self.globalHotkeys.append(self.toggleServiceHotkey) #_logger.debug("Global hotkeys: %s", self.globalHotkeys) #_logger.debug("Hotkey folders: %s", self.hotKeyFolders) #_logger.debug("Hotkey phrases: %s", self.hotKeys) #_logger.debug("Abbreviation phrases: %s", self.abbreviations) #_logger.debug("All folders: %s", self.allFolders) #_logger.debug("All phrases: %s", self.allItems) if persistGlobal: save_config(self) self.lock.release() def __processFolder(self, parentFolder): if not self.app.monitor.has_watch(parentFolder.path): self.app.monitor.add_watch(parentFolder.path) for folder in parentFolder.folders: if TriggerMode.HOTKEY in folder.modes: self.hotKeyFolders.append(folder) self.allFolders.append(folder) if not self.app.monitor.has_watch(folder.path): self.app.monitor.add_watch(folder.path) self.__processFolder(folder) for item in parentFolder.items: if TriggerMode.HOTKEY in item.modes: self.hotKeys.append(item) if TriggerMode.ABBREVIATION in item.modes: self.abbreviations.append(item) self.allItems.append(item) # TODO Future functionality def add_recent_entry(self, entry): if not self.folders.has_key(RECENT_ENTRIES_FOLDER): folder = Folder(RECENT_ENTRIES_FOLDER) folder.set_hotkey([""], "") folder.set_modes([TriggerMode.HOTKEY]) self.folders[RECENT_ENTRIES_FOLDER] = folder self.recentEntries = [] folder = self.folders[RECENT_ENTRIES_FOLDER] if not entry in self.recentEntries: self.recentEntries.append(entry) while len(self.recentEntries) > self.SETTINGS[RECENT_ENTRY_COUNT]: self.recentEntries.pop(0) folder.items = [] for theEntry in self.recentEntries: if len(theEntry) > 17: description = theEntry[:17] + "..." else: description = theEntry p = Phrase(description, theEntry) if self.SETTINGS[RECENT_ENTRY_SUGGEST]: p.set_modes([TriggerMode.PREDICTIVE]) folder.add_item(p) self.config_altered(False) def check_abbreviation_unique(self, abbreviation, newFilterPattern, targetItem): """ Checks that the given abbreviation is not already in use. @param abbreviation: the abbreviation to check @param targetItem: the phrase for which the abbreviation to be used """ for item in self.allFolders: if TriggerMode.ABBREVIATION in item.modes: if abbreviation in item.abbreviations and item.filter_matches(newFilterPattern): return item is targetItem, item for item in self.allItems: if TriggerMode.ABBREVIATION in item.modes: if abbreviation in item.abbreviations and item.filter_matches(newFilterPattern): return item is targetItem, item return True, None """def check_abbreviation_substring(self, abbreviation, targetItem): for item in self.allFolders: if TriggerMode.ABBREVIATION in item.modes: if abbreviation in item.abbreviation or item.abbreviation in abbreviation: return item is targetItem, item.title for item in self.allItems: if TriggerMode.ABBREVIATION in item.modes: if abbreviation in item.abbreviation or item.abbreviation in abbreviation: return item is targetItem, item.description return True, "" def __checkSubstringAbbr(self, item1, item2, abbr): # Check if the given abbreviation is a substring match for the given item # If it is, check a few other rules to see if it matters print "substring check %s against %s" % (item.abbreviation, abbr) try: index = item.abbreviation.index(abbr) print index if index == 0 and len(abbr) < len(item.abbreviation): return item.immediate elif (index + len(abbr)) == len(item.abbreviation): return item.triggerInside elif len(abbr) != len(item.abbreviation): return item.triggerInside and item.immediate else: return False except ValueError: return False""" def check_hotkey_unique(self, modifiers, hotKey, newFilterPattern, targetItem): """ Checks that the given hotkey is not already in use. Also checks the special hotkeys configured from the advanced settings dialog. @param modifiers: modifiers for the hotkey @param hotKey: the hotkey to check @param targetItem: the phrase for which the hotKey to be used """ for item in self.allFolders: if TriggerMode.HOTKEY in item.modes: if item.modifiers == modifiers and item.hotKey == hotKey and item.filter_matches(newFilterPattern): return item is targetItem, item for item in self.allItems: if TriggerMode.HOTKEY in item.modes: if item.modifiers == modifiers and item.hotKey == hotKey and item.filter_matches(newFilterPattern): return item is targetItem, item for item in self.globalHotkeys: if item.enabled: if item.modifiers == modifiers and item.hotKey == hotKey and item.filter_matches(newFilterPattern): return item is targetItem, item return True, None # This import placed here to prevent circular import conflicts from model import * class GlobalHotkey(AbstractHotkey): """ A global application hotkey, configured from the advanced settings dialog. Allows a method call to be attached to the hotkey. """ def __init__(self): AbstractHotkey.__init__(self) self.enabled = False self.windowInfoRegex = None self.isRecursive = False self.parent = None def get_serializable(self): d = { "enabled": self.enabled } d.update(AbstractHotkey.get_serializable(self)) return d def load_from_serialized(self, data): AbstractHotkey.load_from_serialized(self, data) self.enabled = data["enabled"] def set_closure(self, closure): """ Set the callable to be executed when the hotkey is triggered. """ self.closure = closure def check_hotkey(self, modifiers, key, windowTitle): if AbstractHotkey.check_hotkey(self, modifiers, key, windowTitle) and self.enabled: _logger.debug("Triggered global hotkey using modifiers: %r key: %r", modifiers, key) self.closure() return False def get_hotkey_string(self, key=None, modifiers=None): if key is None and modifiers is None: if not self.enabled: return "" key = self.hotKey modifiers = self.modifiers ret = "" for modifier in modifiers: ret += modifier ret += "+" if key == ' ': ret += "" else: ret += key return ret def __str__(self): return _("AutoKey global hotkeys") autokey-0.90.4/src/lib/model.py0000664000175000017500000011076711747642707015333 0ustar chrischris# -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import re, os, os.path, glob, logging from configmanager import * from iomediator import Key, NAVIGATION_KEYS, KEY_SPLIT_RE from scripting import Store _logger = logging.getLogger("model") DEFAULT_WORDCHAR_REGEX = '[\w]' JSON_FILE_PATTERN = "%s/.%s.json" SPACES_RE = re.compile(r"^ | $") def make_wordchar_re(wordChars): return "[^%s]" % wordChars def extract_wordchars(regex): return regex[2:-1] def get_value_or_default(jsonData, key, default): if key in jsonData: return jsonData[key] else: return default def get_safe_path(basePath, name, ext=""): name = SPACES_RE.sub('_', name) safeName = ''.join([char for char in name if char.isalnum() or char in "_ -."]) if safeName == '': path = basePath + '/1' + ext jsonPath = basePath + "/.1.json" n = 2 else: path = basePath + '/' + safeName + ext jsonPath = basePath + '/.' + safeName + ".json" n = 1 while os.path.exists(path) or os.path.exists(jsonPath): path = basePath + '/' + safeName + str(n) + ext jsonPath = basePath + '/.' + safeName + str(n) + ".json" n += 1 return path class AbstractAbbreviation: """ Abstract class encapsulating the common functionality of an abbreviation list """ def __init__(self): self.abbreviations = [] self.backspace = True self.ignoreCase = False self.immediate = False self.triggerInside = False self.set_word_chars(DEFAULT_WORDCHAR_REGEX) def get_serializable(self): d = { "abbreviations": self.abbreviations, "backspace": self.backspace, "ignoreCase": self.ignoreCase, "immediate": self.immediate, "triggerInside": self.triggerInside, "wordChars": self.get_word_chars() } return d def load_from_serialized(self, data): if "abbreviations" not in data: # check for pre v0.80.4 self.abbreviations = [data["abbreviation"]] else: self.abbreviations = data["abbreviations"] self.backspace = data["backspace"] self.ignoreCase = data["ignoreCase"] self.immediate = data["immediate"] self.triggerInside = data["triggerInside"] self.set_word_chars(data["wordChars"]) def copy_abbreviation(self, abbr): self.abbreviations = abbr.abbreviations self.backspace = abbr.backspace self.ignoreCase = abbr.ignoreCase self.immediate = abbr.immediate self.triggerInside = abbr.triggerInside self.set_word_chars(abbr.get_word_chars()) def set_word_chars(self, regex): self.wordChars = re.compile(regex, re.UNICODE) def get_word_chars(self): return self.wordChars.pattern def add_abbreviation(self, abbr): self.abbreviations.append(abbr) def clear_abbreviations(self): self.abbreviations = [] def get_abbreviations(self): if TriggerMode.ABBREVIATION not in self.modes: return "" elif len(self.abbreviations) == 1: return self.abbreviations[0] else: return u"[%s]" % u','.join(self.abbreviations) def _should_trigger_abbreviation(self, buffer): """ Checks whether, based on the settings for the abbreviation and the given input, the abbreviation should trigger. @param buffer Input buffer to be checked (as string) """ for abbr in self.abbreviations: if self.__checkInput(buffer, abbr): return True return False def _get_trigger_abbreviation(self, buffer): for abbr in self.abbreviations: if self.__checkInput(buffer, abbr): return abbr return None def __checkInput(self, buffer, abbr): stringBefore, typedAbbr, stringAfter = self._partition_input(buffer, abbr) if len(typedAbbr) > 0: # Check trigger character condition if not self.immediate: # If not immediate expansion, check last character if len(stringAfter) == 1: # Have a character after abbr if self.wordChars.match(stringAfter): # last character(s) is a word char, can't send expansion return False elif len(stringAfter) > 1: # Abbr not at/near end of buffer any more, can't send return False else: # Nothing after abbr yet, can't expand yet return False else: # immediate option enabled, check abbr is at end of buffer if len(stringAfter) > 0: return False # Check chars ahead of abbr # length of stringBefore should always be > 0 if len(stringBefore) > 0: if self.wordChars.match(stringBefore[-1]): # last char before is a word char if not self.triggerInside: # can't trigger when inside a word return False return True return False def _partition_input(self, currentString, abbr): """ Partition the input into text before, text after, and typed abbreviation (if it exists) """ if self.ignoreCase: matchString = currentString.lower() stringBefore, typedAbbr, stringAfter = matchString.rpartition(abbr) abbrStart = len(stringBefore) abbrEnd = abbrStart + len(typedAbbr) typedAbbr = currentString[abbrStart:abbrEnd] else: stringBefore, typedAbbr, stringAfter = currentString.rpartition(abbr) return (stringBefore, typedAbbr, stringAfter) class AbstractWindowFilter: def __init__(self): self.windowInfoRegex = None self.isRecursive = False def get_serializable(self): if self.windowInfoRegex is not None: return {"regex": self.windowInfoRegex.pattern, "isRecursive": self.isRecursive} else: return {"regex": None, "isRecursive": False} def load_from_serialized(self, data): if isinstance(data, dict): # check needed for data from versions < 0.80.4 self.set_window_titles(data["regex"]) self.isRecursive = data["isRecursive"] else: self.set_window_titles(data) def copy_window_filter(self, filter): self.windowInfoRegex = filter.windowInfoRegex self.isRecursive = filter.isRecursive def set_window_titles(self, regex): if regex is not None: self.windowInfoRegex = re.compile(regex, re.UNICODE) else: self.windowInfoRegex = regex def set_filter_recursive(self, recurse): self.isRecursive = recurse def has_filter(self): return self.windowInfoRegex is not None def inherits_filter(self): if self.parent is not None: return self.parent.get_applicable_regex(True) is not None return False def get_child_filter(self): if self.isRecursive and self.windowInfoRegex is not None: return self.get_filter_regex() + _(" (Inherited)") elif self.parent is not None: return self.parent.get_child_filter() else: return "" def get_filter_regex(self): """ Used by the GUI to obtain human-readable version of the filter """ if self.windowInfoRegex is not None: if self.isRecursive: return self.windowInfoRegex.pattern + _(" (Recursive)") else: return self.windowInfoRegex.pattern elif self.parent is not None: return self.parent.get_child_filter() else: return "" def filter_matches(self, otherFilter): if otherFilter is None or self.get_applicable_regex() is None: return True return otherFilter == self.get_applicable_regex().pattern def get_applicable_regex(self, forChild=False): if self.windowInfoRegex is not None: if (forChild and self.isRecursive) or not forChild: return self.windowInfoRegex elif self.parent is not None: return self.parent.get_applicable_regex(True) return None def _should_trigger_window_title(self, windowInfo): r = self.get_applicable_regex() if r is not None: return r.match(windowInfo[0]) or r.match(windowInfo[1]) else: return True class AbstractHotkey(AbstractWindowFilter): def __init__(self): self.modifiers = [] self.hotKey = None def get_serializable(self): d = { "modifiers": self.modifiers, "hotKey": self.hotKey } return d def load_from_serialized(self, data): self.set_hotkey(data["modifiers"], data["hotKey"]) def copy_hotkey(self, theHotkey): [self.modifiers.append(modifier) for modifier in theHotkey.modifiers] self.hotKey = theHotkey.hotKey def set_hotkey(self, modifiers, key): modifiers.sort() self.modifiers = modifiers self.hotKey = key def check_hotkey(self, modifiers, key, windowTitle): if self.hotKey is not None and self._should_trigger_window_title(windowTitle): return (self.modifiers == modifiers) and (self.hotKey == key) else: return False def get_hotkey_string(self, key=None, modifiers=None): if key is None and modifiers is None: if TriggerMode.HOTKEY not in self.modes: return "" key = self.hotKey modifiers = self.modifiers ret = "" for modifier in modifiers: ret += modifier ret += "+" if key == ' ': ret += "" else: ret += key return ret class Folder(AbstractAbbreviation, AbstractHotkey, AbstractWindowFilter): """ Manages a collection of subfolders/phrases/scripts, which may be associated with an abbreviation or hotkey. """ def __init__(self, title, showInTrayMenu=False, path=None): AbstractAbbreviation.__init__(self) AbstractHotkey.__init__(self) AbstractWindowFilter.__init__(self) self.title = title self.folders = [] self.items = [] self.modes = [] self.usageCount = 0 self.showInTrayMenu = showInTrayMenu self.parent = None self.path = path def build_path(self, baseName=None): if baseName is None: baseName = self.title if self.parent is not None: self.path = get_safe_path(self.parent.path, baseName) else: self.path = get_safe_path(CONFIG_DEFAULT_FOLDER, baseName) def persist(self): if self.path is None: self.build_path() if not os.path.exists(self.path): os.mkdir(self.path) with open(self.path + "/.folder.json", 'w') as outFile: json.dump(self.get_serializable(), outFile, indent=4) def get_serializable(self): d = { "type": "folder", "title": self.title, #"folders": [folder.get_serializable() for folder in self.folders], #"items": [item.get_serializable() for item in self.items], "modes": self.modes, "usageCount": self.usageCount, "showInTrayMenu": self.showInTrayMenu, "abbreviation": AbstractAbbreviation.get_serializable(self), "hotkey": AbstractHotkey.get_serializable(self), "filter": AbstractWindowFilter.get_serializable(self), #"isTopLevel": self.isTopLevel } return d def load(self, parent=None): self.parent = parent if os.path.exists(self.path + "/.folder.json"): self.load_from_serialized() else: self.title = os.path.basename(self.path) self.load_children() def load_children(self): entries = glob.glob(self.path + "/*") self.folders = [] self.items = [] for entryPath in entries: #entryPath = self.path + '/' + entry if os.path.isdir(entryPath): f = Folder("", path=entryPath) f.load(self) self.folders.append(f) if os.path.isfile(entryPath): i = None if entryPath.endswith(".txt"): i = Phrase("", "", path=entryPath) elif entryPath.endswith(".py"): i = Script("", "", path=entryPath) if i is not None: i.load(self) self.items.append(i) def load_from_serialized(self): try: with open(self.path + "/.folder.json", 'r') as inFile: data = json.load(inFile) self.inject_json_data(data) except Exception: _logger.exception("Error while loading json data for %s", self.title) _logger.error("JSON data not loaded (or loaded incomplete)") def inject_json_data(self, data): self.title = data["title"] self.modes = data["modes"] self.usageCount = data["usageCount"] self.showInTrayMenu = data["showInTrayMenu"] AbstractAbbreviation.load_from_serialized(self, data["abbreviation"]) AbstractHotkey.load_from_serialized(self, data["hotkey"]) AbstractWindowFilter.load_from_serialized(self, data["filter"]) def rebuild_path(self): if self.path is not None: oldName = self.path self.path = get_safe_path(os.path.split(oldName)[0], self.title) self.update_children() os.rename(oldName, self.path) else: self.build_path() def update_children(self): for childFolder in self.folders: childFolder.build_path(os.path.basename(childFolder.path)) childFolder.update_children() for childItem in self.items: childItem.build_path(os.path.basename(childItem.path)) def remove_data(self): if self.path is not None: try: shutil.rmtree(self.path) except OSError: pass def get_tuple(self): return ("folder", self.title, self.get_abbreviations(), self.get_hotkey_string(), self) def set_modes(self, modes): self.modes = modes def add_folder(self, folder): folder.parent = self #self.folders[folder.title] = folder self.folders.append(folder) def remove_folder(self, folder): #del self.folders[folder.title] self.folders.remove(folder) def add_item(self, item): """ Add a new script or phrase to the folder. """ item.parent = self #self.phrases[phrase.description] = phrase self.items.append(item) def remove_item(self, item): """ Removes the given phrase or script from the folder. """ #del self.phrases[phrase.description] self.items.remove(item) def set_modes(self, modes): self.modes = modes def check_input(self, buffer, windowInfo): if TriggerMode.ABBREVIATION in self.modes: return self._should_trigger_abbreviation(buffer) and self._should_trigger_window_title(windowInfo) else: return False def increment_usage_count(self): self.usageCount += 1 if self.parent is not None: self.parent.increment_usage_count() def get_backspace_count(self, buffer): """ Given the input buffer, calculate how many backspaces are needed to erase the text that triggered this folder. """ if TriggerMode.ABBREVIATION in self.modes and self.backspace: if self._should_trigger_abbreviation(buffer): abbr = self._get_trigger_abbreviation(buffer) stringBefore, typedAbbr, stringAfter = self._partition_input(buffer, abbr) return len(abbr) + len(stringAfter) if self.parent is not None: return self.parent.get_backspace_count(buffer) return 0 def calculate_input(self, buffer): """ Calculate how many keystrokes were used in triggering this folder (if applicable). """ if TriggerMode.ABBREVIATION in self.modes and self.backspace: if self._should_trigger_abbreviation(buffer): if self.immediate: return len(self.abbreviation) else: return len(self.abbreviation) + 1 if self.parent is not None: return self.parent.calculate_input(buffer) return 0 """def __cmp__(self, other): if self.usageCount != other.usageCount: return cmp(self.usageCount, other.usageCount) else: return cmp(other.title, self.title)""" def __str__(self): return "folder '%s'" % self.title def __repr__(self): return str(self) class TriggerMode: """ Enumeration class for phrase match modes. NONE: Don't trigger this phrase (phrase will only be shown in its folder). ABBREVIATION: Trigger this phrase using an abbreviation. PREDICTIVE: Trigger this phrase using predictive mode. """ NONE = 0 ABBREVIATION = 1 PREDICTIVE = 2 HOTKEY = 3 class SendMode: """ Enumeration class for phrase send modes KEYBOARD: Send using key events CB_CTRL_V: Send via clipboard and paste with Ctrl+v CB_CTRL_SHIFT_V: Send via clipboard and paste with Ctrl+Shift+v SELECTION: Send via X selection and paste with middle mouse button """ KEYBOARD = "kb" CB_CTRL_V = Key.CONTROL + "+v" CB_CTRL_SHIFT_V = Key.CONTROL + '+' + Key.SHIFT + "+v" CB_SHIFT_INSERT = Key.SHIFT + '+' + Key.INSERT SELECTION = None SEND_MODES = { "Keyboard" : SendMode.KEYBOARD, "Clipboard (Ctrl+V)" : SendMode.CB_CTRL_V, "Clipboard (Ctrl+Shift+V)" : SendMode.CB_CTRL_SHIFT_V, "Clipboard (Shift+Insert)" : SendMode.CB_SHIFT_INSERT, "Mouse Selection" : SendMode.SELECTION } class Phrase(AbstractAbbreviation, AbstractHotkey, AbstractWindowFilter): """ Encapsulates all data and behaviour for a phrase. """ def __init__(self, description, phrase, path=None): AbstractAbbreviation.__init__(self) AbstractHotkey.__init__(self) AbstractWindowFilter.__init__(self) self.description = description self.phrase = phrase self.modes = [] self.usageCount = 0 self.prompt = False self.omitTrigger = False self.matchCase = False self.parent = None self.showInTrayMenu = False self.sendMode = SendMode.KEYBOARD self.path = path def build_path(self, baseName=None): if baseName is None: baseName = self.description else: baseName = baseName[:-4] self.path = get_safe_path(self.parent.path, baseName, ".txt") def get_json_path(self): directory, baseName = os.path.split(self.path[:-4]) return JSON_FILE_PATTERN % (directory, baseName) def persist(self): if self.path is None: self.build_path() with open(self.get_json_path(), 'w') as jsonFile: json.dump(self.get_serializable(), jsonFile, indent=4) with open(self.path, "w") as outFile: outFile.write(self.phrase.encode("utf-8")) def get_serializable(self): d = { "type": "phrase", "description": self.description, #"phrase": self.phrase, "modes": self.modes, "usageCount": self.usageCount, "prompt": self.prompt, "omitTrigger": self.omitTrigger, "matchCase": self.matchCase, "showInTrayMenu": self.showInTrayMenu, "abbreviation": AbstractAbbreviation.get_serializable(self), "hotkey": AbstractHotkey.get_serializable(self), "filter": AbstractWindowFilter.get_serializable(self), "sendMode" : self.sendMode } return d def load(self, parent): self.parent = parent with open(self.path, "r") as inFile: self.phrase = inFile.read().decode("utf-8") if os.path.exists(self.get_json_path()): self.load_from_serialized() else: self.description = os.path.basename(self.path)[:-4] def load_from_serialized(self): try: with open(self.get_json_path(), "r") as jsonFile: data = json.load(jsonFile) self.inject_json_data(data) except Exception: _logger.exception("Error while loading json data for %s", self.description) _logger.error("JSON data not loaded (or loaded incomplete)") def inject_json_data(self, data): self.description = data["description"] self.modes = data["modes"] self.usageCount = data["usageCount"] self.prompt = data["prompt"] self.omitTrigger = data["omitTrigger"] self.matchCase = data["matchCase"] self.showInTrayMenu = data["showInTrayMenu"] self.sendMode = get_value_or_default(data, "sendMode", SendMode.KEYBOARD) AbstractAbbreviation.load_from_serialized(self, data["abbreviation"]) AbstractHotkey.load_from_serialized(self, data["hotkey"]) AbstractWindowFilter.load_from_serialized(self, data["filter"]) def rebuild_path(self): if self.path is not None: oldName = self.path oldJson = self.get_json_path() self.build_path() os.rename(oldName, self.path) os.rename(oldJson, self.get_json_path()) else: self.build_path() def remove_data(self): if self.path is not None: if os.path.exists(self.path): os.remove(self.path) if os.path.exists(self.get_json_path()): os.remove(self.get_json_path()) def copy(self, thePhrase): self.description = thePhrase.description self.phrase = thePhrase.phrase # TODO - re-enable me if restoring predictive functionality #if TriggerMode.PREDICTIVE in thePhrase.modes: # self.modes.append(TriggerMode.PREDICTIVE) self.prompt = thePhrase.prompt self.omitTrigger = thePhrase.omitTrigger self.matchCase = thePhrase.matchCase self.parent = thePhrase.parent self.showInTrayMenu = thePhrase.showInTrayMenu self.copy_abbreviation(thePhrase) self.copy_hotkey(thePhrase) self.copy_window_filter(thePhrase) def get_tuple(self): return ("text-plain", self.description, self.get_abbreviations(), self.get_hotkey_string(), self) def set_modes(self, modes): self.modes = modes def check_input(self, buffer, windowInfo): if self._should_trigger_window_title(windowInfo): abbr = False predict = False if TriggerMode.ABBREVIATION in self.modes: abbr = self._should_trigger_abbreviation(buffer) # TODO - re-enable me if restoring predictive functionality #if TriggerMode.PREDICTIVE in self.modes: # predict = self._should_trigger_predictive(buffer) return (abbr or predict) return False def build_phrase(self, buffer): self.usageCount += 1 self.parent.increment_usage_count() expansion = Expansion(self.phrase) triggerFound = False if TriggerMode.ABBREVIATION in self.modes: if self._should_trigger_abbreviation(buffer): abbr = self._get_trigger_abbreviation(buffer) stringBefore, typedAbbr, stringAfter = self._partition_input(buffer, abbr) triggerFound = True if self.backspace: # determine how many backspaces to send expansion.backspaces = len(abbr) + len(stringAfter) else: expansion.backspaces = len(stringAfter) if not self.omitTrigger: expansion.string += stringAfter if self.matchCase: if typedAbbr.istitle(): expansion.string = expansion.string.capitalize() elif typedAbbr.isupper(): expansion.string = expansion.string.upper() elif typedAbbr.islower(): expansion.string = expansion.string.lower() # TODO - re-enable me if restoring predictive functionality #if TriggerMode.PREDICTIVE in self.modes: # if self._should_trigger_predictive(buffer): # expansion.string = expansion.string[ConfigManager.SETTINGS[PREDICTIVE_LENGTH]:] # triggerFound = True if not triggerFound: # Phrase could have been triggered from menu - check parents for backspace count expansion.backspaces = self.parent.get_backspace_count(buffer) #self.__parsePositionTokens(expansion) return expansion def calculate_input(self, buffer): """ Calculate how many keystrokes were used in triggering this phrase. """ if TriggerMode.ABBREVIATION in self.modes: if self._should_trigger_abbreviation(buffer): if self.immediate: return len(self.abbreviation) else: return len(self.abbreviation) + 1 # TODO - re-enable me if restoring predictive functionality #if TriggerMode.PREDICTIVE in self.modes: # if self._should_trigger_predictive(buffer): # return ConfigManager.SETTINGS[PREDICTIVE_LENGTH] if TriggerMode.HOTKEY in self.modes: if buffer == '': return len(self.modifiers) + 1 return self.parent.calculate_input(buffer) def get_trigger_chars(self, buffer): abbr = self._get_trigger_abbreviation(buffer) stringBefore, typedAbbr, stringAfter = self._partition_input(buffer, abbr) return typedAbbr + stringAfter def should_prompt(self, buffer): """ Get a value indicating whether the user should be prompted to select the phrase. Always returns true if the phrase has been triggered using predictive mode. """ # TODO - re-enable me if restoring predictive functionality #if TriggerMode.PREDICTIVE in self.modes: # if self._should_trigger_predictive(buffer): # return True return self.prompt def get_description(self, buffer): # TODO - re-enable me if restoring predictive functionality #if self._should_trigger_predictive(buffer): # length = ConfigManager.SETTINGS[PREDICTIVE_LENGTH] # endPoint = length + 30 # if len(self.phrase) > endPoint: # description = "... " + self.phrase[length:endPoint] + "..." # else: # description = "... " + self.phrase[length:] # description = description.replace('\n', ' ') # return description #else: return self.description # TODO - re-enable me if restoring predictive functionality """def _should_trigger_predictive(self, buffer): if len(buffer) >= ConfigManager.SETTINGS[PREDICTIVE_LENGTH]: typed = buffer[-ConfigManager.SETTINGS[PREDICTIVE_LENGTH]:] return self.phrase.startswith(typed) else: return False""" def parsePositionTokens(self, expansion): # Check the string for cursor positioning token and apply lefts and ups as appropriate if CURSOR_POSITION_TOKEN in expansion.string: firstpart, secondpart = expansion.string.split(CURSOR_POSITION_TOKEN) foundNavigationKey = False for key in NAVIGATION_KEYS: if key in expansion.string: expansion.lefts = 0 foundNavigationKey = True break if not foundNavigationKey: k = Key() for section in KEY_SPLIT_RE.split(secondpart): if not k.is_key(section) or section in [' ', '\n']: expansion.lefts += len(section) expansion.string = firstpart + secondpart def __str__(self): return "phrase '%s'" % self.description def __repr__(self): return "Phrase('" + self.description + "')" class Expansion: def __init__(self, string): self.string = string self.lefts = 0 self.backspaces = 0 class Script(AbstractAbbreviation, AbstractHotkey, AbstractWindowFilter): """ Encapsulates all data and behaviour for a script. """ def __init__(self, description, code, path=None): AbstractAbbreviation.__init__(self) AbstractHotkey.__init__(self) AbstractWindowFilter.__init__(self) self.description = description self.code = code self.store = Store() self.modes = [] self.usageCount = 0 self.prompt = False self.omitTrigger = False self.parent = None self.showInTrayMenu = False self.path = path def build_path(self, baseName=None): if baseName is None: baseName = self.description else: baseName = baseName[:-3] self.path = get_safe_path(self.parent.path, baseName, ".py") def get_json_path(self): directory, baseName = os.path.split(self.path[:-3]) return JSON_FILE_PATTERN % (directory, baseName) def persist(self): if self.path is None: self.build_path() with open(self.get_json_path(), 'w') as jsonFile: json.dump(self.get_serializable(), jsonFile, indent=4) with open(self.path, "w") as outFile: outFile.write(self.code.encode("utf-8")) def get_serializable(self): d = { "type": "script", "description": self.description, #"code": self.code, "store": self.store, "modes": self.modes, "usageCount": self.usageCount, "prompt": self.prompt, "omitTrigger": self.omitTrigger, "showInTrayMenu": self.showInTrayMenu, "abbreviation": AbstractAbbreviation.get_serializable(self), "hotkey": AbstractHotkey.get_serializable(self), "filter": AbstractWindowFilter.get_serializable(self) } return d def load(self, parent): self.parent = parent with open(self.path, "r") as inFile: self.code = inFile.read().decode("utf-8") if os.path.exists(self.get_json_path()): self.load_from_serialized() else: self.description = os.path.basename(self.path)[:-3] def load_from_serialized(self): try: with open(self.get_json_path(), "r") as jsonFile: data = json.load(jsonFile) self.inject_json_data(data) except Exception: _logger.exception("Error while loading json data for %s", self.description) _logger.error("JSON data not loaded (or loaded incomplete)") def inject_json_data(self, data): self.description = data["description"] self.store = Store(data["store"]) self.modes = data["modes"] self.usageCount = data["usageCount"] self.prompt = data["prompt"] self.omitTrigger = data["omitTrigger"] self.showInTrayMenu = data["showInTrayMenu"] AbstractAbbreviation.load_from_serialized(self, data["abbreviation"]) AbstractHotkey.load_from_serialized(self, data["hotkey"]) AbstractWindowFilter.load_from_serialized(self, data["filter"]) def rebuild_path(self): if self.path is not None: oldName = self.path oldJson = self.get_json_path() self.build_path() os.rename(oldName, self.path) os.rename(oldJson, self.get_json_path()) else: self.build_path() def remove_data(self): if self.path is not None: if os.path.exists(self.path): os.remove(self.path) if os.path.exists(self.get_json_path()): os.remove(self.get_json_path()) def copy(self, theScript): self.description = theScript.description self.code = theScript.code self.prompt = theScript.prompt self.omitTrigger = theScript.omitTrigger self.parent = theScript.parent self.showInTrayMenu = theScript.showInTrayMenu self.copy_abbreviation(theScript) self.copy_hotkey(theScript) self.copy_window_filter(theScript) def get_tuple(self): return ("text-x-python", self.description, self.get_abbreviations(), self.get_hotkey_string(), self) def set_modes(self, modes): self.modes = modes def check_input(self, buffer, windowInfo): if self._should_trigger_window_title(windowInfo): if TriggerMode.ABBREVIATION in self.modes: return self._should_trigger_abbreviation(buffer) return False def process_buffer(self, buffer): self.usageCount += 1 self.parent.increment_usage_count() triggerFound = False backspaces = 0 string = "" if TriggerMode.ABBREVIATION in self.modes: if self._should_trigger_abbreviation(buffer): abbr = self._get_trigger_abbreviation(buffer) stringBefore, typedAbbr, stringAfter = self._partition_input(buffer, abbr) triggerFound = True if self.backspace: # determine how many backspaces to send backspaces = len(abbr) + len(stringAfter) else: backspaces = len(stringAfter) if not self.omitTrigger: string += stringAfter if not triggerFound: # Phrase could have been triggered from menu - check parents for backspace count backspaces = self.parent.get_backspace_count(buffer) return backspaces, string def should_prompt(self, buffer): return self.prompt def get_description(self, buffer): return self.description def __str__(self): return "script '%s'" % self.description def __repr__(self): return "Script('" + self.description + "')" autokey-0.90.4/src/lib/interface.py0000664000175000017500000012722411754436675016172 0ustar chrischris# -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . __all__ = ["XRecordInterface", "AtSpiInterface"] import os, threading, re, time, socket, select, logging, Queue, subprocess try: import pyatspi HAS_ATSPI = True except ImportError: HAS_ATSPI = False from Xlib import X, XK, display, error try: from Xlib.ext import record, xtest HAS_RECORD = True except ImportError: HAS_RECORD = False from Xlib.protocol import rq, event import common if common.USING_QT: from PyQt4.QtGui import QClipboard, QApplication else: from gi.repository import Gtk, Gdk logger = logging.getLogger("interface") MASK_INDEXES = [ (X.ShiftMapIndex, X.ShiftMask), (X.ControlMapIndex, X.ControlMask), (X.LockMapIndex, X.LockMask), (X.Mod1MapIndex, X.Mod1Mask), (X.Mod2MapIndex, X.Mod2Mask), (X.Mod3MapIndex, X.Mod3Mask), (X.Mod4MapIndex, X.Mod4Mask), (X.Mod5MapIndex, X.Mod5Mask), ] CAPSLOCK_LEDMASK = 1<<0 NUMLOCK_LEDMASK = 1<<1 class XInterfaceBase(threading.Thread): """ Encapsulates the common functionality for the two X interface classes. """ def __init__(self, mediator, app): threading.Thread.__init__(self) self.setDaemon(True) self.setName("XInterface-thread") self.mediator = mediator self.app = app self.lastChars = [] # QT4 Workaround self.__enableQT4Workaround = False # QT4 Workaround self.shutdown = False # Event loop self.eventThread = threading.Thread(target=self.__eventLoop) self.queue = Queue.Queue() # Event listener self.listenerThread = threading.Thread(target=self.__flushEvents) if common.USING_QT: self.clipBoard = QApplication.clipboard() else: self.clipBoard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) self.selection = Gtk.Clipboard.get(Gdk.SELECTION_PRIMARY) self.__initMappings() # Set initial lock state ledMask = self.localDisplay.get_keyboard_control().led_mask mediator.set_modifier_state(Key.CAPSLOCK, (ledMask & CAPSLOCK_LEDMASK) != 0) mediator.set_modifier_state(Key.NUMLOCK, (ledMask & NUMLOCK_LEDMASK) != 0) # Window name atoms self.__NameAtom = self.localDisplay.intern_atom("_NET_WM_NAME", True) self.__VisibleNameAtom = self.localDisplay.intern_atom("_NET_WM_VISIBLE_NAME", True) if not common.USING_QT: self.keyMap = Gdk.Keymap.get_default() self.keyMap.connect("keys-changed", self.on_keys_changed) self.__ignoreRemap = False self.eventThread.start() self.listenerThread.start() def __eventLoop(self): while True: method, args = self.queue.get() if method is None and args is None: break try: method(*args) except Exception, e: logger.exception("Error in X event loop thread") self.queue.task_done() def __enqueue(self, method, *args): self.queue.put_nowait((method, args)) def on_keys_changed(self, data=None): if not self.__ignoreRemap: logger.debug("Recorded keymap change event") self.__ignoreRemap = True time.sleep(0.2) self.__enqueue(self.__ungrabAllHotkeys) self.__enqueue(self.__delayedInitMappings) else: logger.debug("Ignored keymap change event") def __delayedInitMappings(self): self.__initMappings() self.__ignoreRemap = False def __initMappings(self): self.localDisplay = display.Display() self.rootWindow = self.localDisplay.screen().root self.rootWindow.change_attributes(event_mask=X.SubstructureNotifyMask|X.StructureNotifyMask) altList = self.localDisplay.keysym_to_keycodes(XK.XK_ISO_Level3_Shift) self.__usableOffsets = (0, 1) for code, offset in altList: if code == 108 and offset == 0: self.__usableOffsets += (4, 5) logger.debug("Enabling sending using Alt-Grid") break # Build modifier mask mapping self.modMasks = {} mapping = self.localDisplay.get_modifier_mapping() for keySym, ak in XK_TO_AK_MAP.iteritems(): if ak in MODIFIERS: keyCodeList = self.localDisplay.keysym_to_keycodes(keySym) found = False for keyCode, lvl in keyCodeList: for index, mask in MASK_INDEXES: if keyCode in mapping[index]: self.modMasks[ak] = mask found = True break if found: break logger.debug("Modifier masks: %r", self.modMasks) self.__grabHotkeys() self.localDisplay.flush() # --- get list of keycodes that are unused in the current keyboard mapping keyCode = 8 avail = [] for keyCodeMapping in self.localDisplay.get_keyboard_mapping(keyCode, 200): codeAvail = True for offset in keyCodeMapping: if offset != 0: codeAvail = False break if codeAvail: avail.append(keyCode) keyCode += 1 self.__availableKeycodes = avail self.remappedChars = {} if logging.getLogger().getEffectiveLevel() == logging.DEBUG: self.keymap_test() def keymap_test(self): code = self.localDisplay.keycode_to_keysym(108, 0) for attr in XK.__dict__.iteritems(): if attr[0].startswith("XK"): if attr[1] == code: logger.debug("Alt-Grid: %s, %s", attr[0], attr[1]) logger.debug(repr(self.localDisplay.keysym_to_keycodes(XK.XK_ISO_Level3_Shift))) logger.debug("X Server Keymap") for char in "\\|`1234567890-=~!@#$%^&*()qwertyuiop[]asdfghjkl;'zxcvbnm,./QWERTYUIOP{}ASDFGHJKL:\"ZXCVBNM<>?": keyCodeList = self.localDisplay.keysym_to_keycodes(ord(char)) if len(keyCodeList) > 0: logger.debug("[%s] : %s", char, keyCodeList) else: logger.debug("No mapping for [%s]", char) def __needsMutterWorkaround(self, item): if Key.SUPER not in item.modifiers: return False try: output = subprocess.check_output(["ps", "-eo", "command"]) lines = output.splitlines() for line in lines: if "gnome-shell" in line or "cinnamon" in line or "unity" in line: return True except: pass # since this is just a nasty workaround, if anything goes wrong just disable it return False def __grabHotkeys(self): """ Run during startup to grab global and specific hotkeys in all open windows """ c = self.app.configManager hotkeys = c.hotKeys + c.hotKeyFolders # Grab global hotkeys in root window for item in c.globalHotkeys: if item.enabled: self.__enqueue(self.__grabHotkey, item.hotKey, item.modifiers, self.rootWindow) if self.__needsMutterWorkaround(item): self.__enqueue(self.__grabRecurse, item, self.rootWindow, False) # Grab hotkeys without a filter in root window for item in hotkeys: if item.get_applicable_regex() is None: self.__enqueue(self.__grabHotkey, item.hotKey, item.modifiers, self.rootWindow) if self.__needsMutterWorkaround(item): self.__enqueue(self.__grabRecurse, item, self.rootWindow, False) self.__enqueue(self.__recurseTree, self.rootWindow, hotkeys) def __recurseTree(self, parent, hotkeys): # Grab matching hotkeys in all open child windows try: children = parent.query_tree().children except: return # window has been destroyed for window in children: try: title = self.get_window_title(window, False) klass = self.get_window_class(window, False) if title or klass: for item in hotkeys: if item.get_applicable_regex() is not None and item._should_trigger_window_title((title, klass)): self.__grabHotkey(item.hotKey, item.modifiers, window) self.__grabRecurse(item, window, False) self.__enqueue(self.__recurseTree, window, hotkeys) except: logger.exception("grab on window failed") def __ungrabAllHotkeys(self): """ Ungrab all hotkeys in preparation for keymap change """ c = self.app.configManager hotkeys = c.hotKeys + c.hotKeyFolders # Ungrab global hotkeys in root window, recursively for item in c.globalHotkeys: if item.enabled: self.__ungrabHotkey(item.hotKey, item.modifiers, self.rootWindow) if self.__needsMutterWorkaround(item): self.__ungrabRecurse(item, self.rootWindow, False) # Ungrab hotkeys without a filter in root window, recursively for item in hotkeys: if item.get_applicable_regex() is None: self.__ungrabHotkey(item.hotKey, item.modifiers, self.rootWindow) if self.__needsMutterWorkaround(item): self.__ungrabRecurse(item, self.rootWindow, False) self.__recurseTreeUngrab(self.rootWindow, hotkeys) def __recurseTreeUngrab(self, parent, hotkeys): # Ungrab matching hotkeys in all open child windows try: children = parent.query_tree().children except: return # window has been destroyed for window in children: try: title = self.get_window_title(window, False) klass = self.get_window_class(window, False) if title or klass: for item in hotkeys: if item.get_applicable_regex() is not None and item._should_trigger_window_title((title, klass)): self.__ungrabHotkey(item.hotKey, item.modifiers, window) self.__ungrabRecurse(item, window, False) self.__enqueue(self.__recurseTreeUngrab, window, hotkeys) except: logger.exception("ungrab on window failed") def __grabHotkeysForWindow(self, window): """ Grab all hotkeys relevant to the window Used when a new window is created """ c = self.app.configManager hotkeys = c.hotKeys + c.hotKeyFolders title = self.get_window_title(window) klass = self.get_window_class(window) for item in hotkeys: if item.get_applicable_regex() is not None and item._should_trigger_window_title((title, klass)): self.__enqueue(self.__grabHotkey, item.hotKey, item.modifiers, window) elif self.__needsMutterWorkaround(item): self.__enqueue(self.__grabHotkey, item.hotKey, item.modifiers, window) def __grabHotkey(self, key, modifiers, window): """ Grab a specific hotkey in the given window """ logger.debug("Grabbing hotkey: %r %r", modifiers, key) try: keycode = self.__lookupKeyCode(key) mask = 0 for mod in modifiers: mask |= self.modMasks[mod] window.grab_key(keycode, mask, True, X.GrabModeAsync, X.GrabModeAsync) if Key.NUMLOCK in self.modMasks: window.grab_key(keycode, mask|self.modMasks[Key.NUMLOCK], True, X.GrabModeAsync, X.GrabModeAsync) if Key.CAPSLOCK in self.modMasks: window.grab_key(keycode, mask|self.modMasks[Key.CAPSLOCK], True, X.GrabModeAsync, X.GrabModeAsync) if Key.CAPSLOCK in self.modMasks and Key.NUMLOCK in self.modMasks: window.grab_key(keycode, mask|self.modMasks[Key.CAPSLOCK]|self.modMasks[Key.NUMLOCK], True, X.GrabModeAsync, X.GrabModeAsync) except Exception, e: logger.warn("Failed to grab hotkey %r %r: %s", modifiers, key, str(e)) def grab_hotkey(self, item): """ Grab a hotkey. If the hotkey has no filter regex, it is global and is grabbed recursively from the root window If it has a filter regex, iterate over all children of the root and grab from matching windows """ if item.get_applicable_regex() is None: self.__enqueue(self.__grabHotkey, item.hotKey, item.modifiers, self.rootWindow) if self.__needsMutterWorkaround(item): self.__enqueue(self.__grabRecurse, item, self.rootWindow, False) else: self.__enqueue(self.__grabRecurse, item, self.rootWindow) def __grabRecurse(self, item, parent, checkWinInfo=True): try: children = parent.query_tree().children except: return # window has been destroyed for window in children: shouldTrigger = False if checkWinInfo: title = self.get_window_title(window, False) klass = self.get_window_class(window, False) shouldTrigger = item._should_trigger_window_title((title, klass)) if shouldTrigger or not checkWinInfo: self.__grabHotkey(item.hotKey, item.modifiers, window) self.__grabRecurse(item, window, False) else: self.__grabRecurse(item, window) def ungrab_hotkey(self, item): """ Ungrab a hotkey. If the hotkey has no filter regex, it is global and is grabbed recursively from the root window If it has a filter regex, iterate over all children of the root and ungrab from matching windows """ import copy newItem = copy.copy(item) if item.get_applicable_regex() is None: self.__enqueue(self.__ungrabHotkey, newItem.hotKey, newItem.modifiers, self.rootWindow) if self.__needsMutterWorkaround(item): self.__enqueue(self.__ungrabRecurse, newItem, self.rootWindow, False) else: self.__enqueue(self.__ungrabRecurse, newItem, self.rootWindow) def __ungrabRecurse(self, item, parent, checkWinInfo=True): try: children = parent.query_tree().children except: return # window has been destroyed for window in children: shouldTrigger = False if checkWinInfo: title = self.get_window_title(window, False) klass = self.get_window_class(window, False) shouldTrigger = item._should_trigger_window_title((title, klass)) if shouldTrigger or not checkWinInfo: self.__ungrabHotkey(item.hotKey, item.modifiers, window) self.__ungrabRecurse(item, window, False) else: self.__ungrabRecurse(item, window) def __ungrabHotkey(self, key, modifiers, window): """ Ungrab a specific hotkey in the given window """ logger.debug("Ungrabbing hotkey: %r %r", modifiers, key) try: keycode = self.__lookupKeyCode(key) mask = 0 for mod in modifiers: mask |= self.modMasks[mod] window.ungrab_key(keycode, mask) if Key.NUMLOCK in self.modMasks: window.ungrab_key(keycode, mask|self.modMasks[Key.NUMLOCK]) if Key.CAPSLOCK in self.modMasks: window.ungrab_key(keycode, mask|self.modMasks[Key.CAPSLOCK]) if Key.CAPSLOCK in self.modMasks and Key.NUMLOCK in self.modMasks: window.ungrab_key(keycode, mask|self.modMasks[Key.CAPSLOCK]|self.modMasks[Key.NUMLOCK]) except Exception, e: logger.warn("Failed to ungrab hotkey %r %r: %s", modifiers, key, str(e)) def lookup_string(self, keyCode, shifted, numlock, altGrid): if keyCode == 0: return "" keySym = self.localDisplay.keycode_to_keysym(keyCode, 0) if keySym in XK_TO_AK_NUMLOCKED and numlock and not (numlock and shifted): return XK_TO_AK_NUMLOCKED[keySym] elif keySym in XK_TO_AK_MAP: return XK_TO_AK_MAP[keySym] else: try: index = 0 if shifted: index += 1 if altGrid: index += 4 return unichr(self.localDisplay.keycode_to_keysym(keyCode, index)) except ValueError: return "" % keyCode def send_string_clipboard(self, string, pasteCommand): self.__enqueue(self.__sendStringClipboard, string, pasteCommand) def __sendStringClipboard(self, string, pasteCommand): logger.debug("Sending string: %r", string) if pasteCommand is None: if common.USING_QT: self.sem = threading.Semaphore(0) self.app.exec_in_main(self.__fillSelection, string) self.sem.acquire() else: self.__fillSelection(string) focus = self.localDisplay.get_input_focus().focus xtest.fake_input(focus, X.ButtonPress, X.Button2) xtest.fake_input(focus, X.ButtonRelease, X.Button2) else: if common.USING_QT: self.sem = threading.Semaphore(0) self.app.exec_in_main(self.__fillClipboard, string) self.sem.acquire() else: self.__fillClipboard(string) self.mediator.send_string(pasteCommand) if common.USING_QT: self.app.exec_in_main(self.__restoreClipboard) logger.debug("Send via clipboard done") def __restoreClipboard(self): if self.__savedClipboard != "": if common.USING_QT: self.clipBoard.setText(self.__savedClipboard, QClipboard.Clipboard) else: Gdk.threads_enter() self.clipBoard.set_text(self.__savedClipboard) Gdk.threads_leave() def __fillSelection(self, string): if common.USING_QT: self.clipBoard.setText(string, QClipboard.Selection) self.sem.release() else: Gdk.threads_enter() self.selection.set_text(string.encode("utf-8")) Gdk.threads_leave() def __fillClipboard(self, string): if common.USING_QT: self.__savedClipboard = self.clipBoard.text() self.clipBoard.setText(string, QClipboard.Clipboard) self.sem.release() else: Gdk.threads_enter() text = self.clipBoard.wait_for_text() self.__savedClipboard = '' if text is not None: self.__savedClipboard = text self.clipBoard.set_text(string.encode("utf-8")) Gdk.threads_leave() def begin_send(self): self.__enqueue(self.__grab_keyboard) def finish_send(self): self.__enqueue(self.__ungrabKeyboard) def grab_keyboard(self): self.__enqueue(self.__grab_keyboard) def __grab_keyboard(self): focus = self.localDisplay.get_input_focus().focus focus.grab_keyboard(True, X.GrabModeAsync, X.GrabModeAsync, X.CurrentTime) self.localDisplay.flush() def ungrab_keyboard(self): self.__enqueue(self.__ungrabKeyboard) def __ungrabKeyboard(self): self.localDisplay.ungrab_keyboard(X.CurrentTime) self.localDisplay.flush() def __findUsableKeycode(self, codeList): for code, offset in codeList: if offset in self.__usableOffsets: return code, offset return None, None def send_string(self, string): self.__enqueue(self.__sendString, string) def __sendString(self, string): """ Send a string of printable characters. """ logger.debug("Sending string: %r", string) # Determine if workaround is needed if not ConfigManager.SETTINGS[ENABLE_QT4_WORKAROUND]: self.__checkWorkaroundNeeded() # First find out if any chars need remapping remapNeeded = False for char in string: keyCodeList = self.localDisplay.keysym_to_keycodes(ord(char)) usableCode, offset = self.__findUsableKeycode(keyCodeList) if usableCode is None and char not in self.remappedChars: remapNeeded = True break # Now we know chars need remapping, do it if remapNeeded: self.__ignoreRemap = True self.remappedChars = {} remapChars = [] for char in string: keyCodeList = self.localDisplay.keysym_to_keycodes(ord(char)) usableCode, offset = self.__findUsableKeycode(keyCodeList) if usableCode is None: remapChars.append(char) logger.debug("Characters requiring remapping: %r", remapChars) availCodes = self.__availableKeycodes logger.debug("Remapping with keycodes in the range: %r", availCodes) mapping = self.localDisplay.get_keyboard_mapping(8, 200) firstCode = 8 for i in xrange(len(availCodes) - 1): code = availCodes[i] sym1 = 0 sym2 = 0 if len(remapChars) > 0: char = remapChars.pop(0) self.remappedChars[char] = (code, 0) sym1 = ord(char) if len(remapChars) > 0: char = remapChars.pop(0) self.remappedChars[char] = (code, 1) sym2 = ord(char) if sym1 != 0: mapping[code - firstCode][0] = sym1 mapping[code - firstCode][1] = sym2 mapping = [tuple(l) for l in mapping] self.localDisplay.change_keyboard_mapping(firstCode, mapping) self.localDisplay.flush() focus = self.localDisplay.get_input_focus().focus for char in string: try: keyCodeList = self.localDisplay.keysym_to_keycodes(ord(char)) keyCode, offset = self.__findUsableKeycode(keyCodeList) if keyCode is not None: if offset == 0: self.__sendKeyCode(keyCode, theWindow=focus) if offset == 1: self.__pressKey(Key.SHIFT) self.__sendKeyCode(keyCode, self.modMasks[Key.SHIFT], focus) self.__releaseKey(Key.SHIFT) if offset == 4: self.__pressKey(Key.ALT_GR) self.__sendKeyCode(keyCode, self.modMasks[Key.ALT_GR], focus) self.__releaseKey(Key.ALT_GR) if offset == 5: self.__pressKey(Key.ALT_GR) self.__pressKey(Key.SHIFT) self.__sendKeyCode(keyCode, self.modMasks[Key.ALT_GR]|self.modMasks[Key.SHIFT], focus) self.__releaseKey(Key.SHIFT) self.__releaseKey(Key.ALT_GR) elif char in self.remappedChars: keyCode, offset = self.remappedChars[char] if offset == 0: self.__sendKeyCode(keyCode, theWindow=focus) if offset == 1: self.__pressKey(Key.SHIFT) self.__sendKeyCode(keyCode, self.modMasks[Key.SHIFT], focus) self.__releaseKey(Key.SHIFT) else: logger.warn("Unable to send character %r", char) except Exception, e: logger.exception("Error sending char %r: %s", char, str(e)) self.__ignoreRemap = False def send_key(self, keyName): """ Send a specific non-printing key, eg Up, Left, etc """ self.__enqueue(self.__sendKey, keyName) def __sendKey(self, keyName): logger.debug("Send special key: [%r]", keyName) self.__sendKeyCode(self.__lookupKeyCode(keyName)) def fake_keypress(self, keyName): self.__enqueue(self.__fakeKeypress, keyName) def __fakeKeypress(self, keyName): keyCode = self.__lookupKeyCode(keyName) xtest.fake_input(self.rootWindow, X.KeyPress, keyCode) xtest.fake_input(self.rootWindow, X.KeyRelease, keyCode) def fake_keydown(self, keyName): self.__enqueue(self.__fakeKeydown, keyName) def __fakeKeydown(self, keyName): keyCode = self.__lookupKeyCode(keyName) xtest.fake_input(self.rootWindow, X.KeyPress, keyCode) def fake_keyup(self, keyName): self.__enqueue(self.__fakeKeyup, keyName) def __fakeKeyup(self, keyName): keyCode = self.__lookupKeyCode(keyName) xtest.fake_input(self.rootWindow, X.KeyRelease, keyCode) def send_modified_key(self, keyName, modifiers): """ Send a modified key (e.g. when emulating a hotkey) """ self.__enqueue(self.__sendModifiedKey, keyName, modifiers) def __sendModifiedKey(self, keyName, modifiers): logger.debug("Send modified key: modifiers: %s key: %s", modifiers, keyName) try: mask = 0 for mod in modifiers: mask |= self.modMasks[mod] keyCode = self.__lookupKeyCode(keyName) for mod in modifiers: self.__pressKey(mod) self.__sendKeyCode(keyCode, mask) for mod in modifiers: self.__releaseKey(mod) except Exception, e: logger.warn("Error sending modified key %r %r: %s", modifiers, keyName, str(e)) def send_mouse_click(self, xCoord, yCoord, button, relative): self.__enqueue(self.__sendMouseClick, xCoord, yCoord, button, relative) def __sendMouseClick(self, xCoord, yCoord, button, relative): # Get current pointer position so we can return it there pos = self.rootWindow.query_pointer() if relative: focus = self.localDisplay.get_input_focus().focus focus.warp_pointer(xCoord, yCoord) xtest.fake_input(focus, X.ButtonPress, button, x=xCoord, y=yCoord) xtest.fake_input(focus, X.ButtonRelease, button, x=xCoord, y=yCoord) else: self.rootWindow.warp_pointer(xCoord, yCoord) xtest.fake_input(self.rootWindow, X.ButtonPress, button, x=xCoord, y=yCoord) xtest.fake_input(self.rootWindow, X.ButtonRelease, button, x=xCoord, y=yCoord) self.rootWindow.warp_pointer(pos.root_x, pos.root_y) self.__flush() def send_mouse_click_relative(self, xoff, yoff, button): self.__enqueue(self.__sendMouseClickRelative, xoff, yoff, button) def __sendMouseClickRelative(self, xoff, yoff, button): # Get current pointer position pos = self.rootWindow.query_pointer() xCoord = pos.root_x + xoff yCoord = pos.root_y + yoff self.rootWindow.warp_pointer(xCoord, yCoord) xtest.fake_input(self.rootWindow, X.ButtonPress, button, x=xCoord, y=yCoord) xtest.fake_input(self.rootWindow, X.ButtonRelease, button, x=xCoord, y=yCoord) self.rootWindow.warp_pointer(pos.root_x, pos.root_y) self.__flush() def flush(self): self.__enqueue(self.__flush) def __flush(self): self.localDisplay.flush() self.lastChars = [] def press_key(self, keyName): self.__enqueue(self.__pressKey, keyName) def __pressKey(self, keyName): self.__sendKeyPressEvent(self.__lookupKeyCode(keyName), 0) def release_key(self, keyName): self.__enqueue(self.__releaseKey, keyName) def __releaseKey(self, keyName): self.__sendKeyReleaseEvent(self.__lookupKeyCode(keyName), 0) def __flushEvents(self): while True: try: readable, w, e = select.select([self.localDisplay], [], [], 1) time.sleep(1) if self.localDisplay in readable: createdWindows = [] destroyedWindows = [] for x in xrange(self.localDisplay.pending_events()): event = self.localDisplay.next_event() if event.type == X.CreateNotify: createdWindows.append(event.window) if event.type == X.DestroyNotify: destroyedWindows.append(event.window) for window in createdWindows: if window not in destroyedWindows: self.__enqueue(self.__grabHotkeysForWindow, window) if self.shutdown: break except: pass def handle_keypress(self, keyCode): self.__enqueue(self.__handleKeyPress, keyCode) def __handleKeyPress(self, keyCode): focus = self.localDisplay.get_input_focus().focus modifier = self.__decodeModifier(keyCode) if modifier is not None: self.mediator.handle_modifier_down(modifier) else: self.mediator.handle_keypress(keyCode, self.get_window_title(focus), self.get_window_class(focus)) def handle_keyrelease(self, keyCode): self.__enqueue(self.__handleKeyrelease, keyCode) def __handleKeyrelease(self, keyCode): modifier = self.__decodeModifier(keyCode) if modifier is not None: self.mediator.handle_modifier_up(modifier) def handle_mouseclick(self, button, x, y): self.__enqueue(self.__handleMouseclick, button, x, y) def __handleMouseclick(self, button, x, y): title = self.get_window_title() klass = self.get_window_class() info = (title, klass) if x is None and y is None: ret = self.localDisplay.get_input_focus().focus.query_pointer() self.mediator.handle_mouse_click(ret.root_x, ret.root_y, ret.win_x, ret.win_y, button, info) else: focus = self.localDisplay.get_input_focus().focus try: rel = focus.translate_coords(self.rootWindow, x, y) self.mediator.handle_mouse_click(x, y, rel.x, rel.y, button, info) except: self.mediator.handle_mouse_click(x, y, 0, 0, button, info) def __decodeModifier(self, keyCode): """ Checks if the given keyCode is a modifier key. If it is, returns the modifier name constant as defined in the iomediator module. If not, returns C{None} """ keyName = self.lookup_string(keyCode, False, False, False) if keyName in MODIFIERS: return keyName return None def __sendKeyCode(self, keyCode, modifiers=0, theWindow=None): if ConfigManager.SETTINGS[ENABLE_QT4_WORKAROUND] or self.__enableQT4Workaround: self.__doQT4Workaround(keyCode) self.__sendKeyPressEvent(keyCode, modifiers, theWindow) self.__sendKeyReleaseEvent(keyCode, modifiers, theWindow) def __checkWorkaroundNeeded(self): focus = self.localDisplay.get_input_focus().focus windowName = self.get_window_title(focus) windowClass = self.get_window_class(focus) w = self.app.configManager.workAroundApps if w.match(windowName) or w.match(windowClass): self.__enableQT4Workaround = True else: self.__enableQT4Workaround = False def __doQT4Workaround(self, keyCode): if len(self.lastChars) > 0: if keyCode in self.lastChars: self.localDisplay.flush() time.sleep(0.0125) self.lastChars.append(keyCode) if len(self.lastChars) > 10: self.lastChars.pop(0) def __sendKeyPressEvent(self, keyCode, modifiers, theWindow=None): if theWindow is None: focus = self.localDisplay.get_input_focus().focus else: focus = theWindow keyEvent = event.KeyPress( detail=keyCode, time=X.CurrentTime, root=self.rootWindow, window=focus, child=X.NONE, root_x=1, root_y=1, event_x=1, event_y=1, state=modifiers, same_screen=1 ) focus.send_event(keyEvent) def __sendKeyReleaseEvent(self, keyCode, modifiers, theWindow=None): if theWindow is None: focus = self.localDisplay.get_input_focus().focus else: focus = theWindow keyEvent = event.KeyRelease( detail=keyCode, time=X.CurrentTime, root=self.rootWindow, window=focus, child=X.NONE, root_x=1, root_y=1, event_x=1, event_y=1, state=modifiers, same_screen=1 ) focus.send_event(keyEvent) def __lookupKeyCode(self, char): if char in AK_TO_XK_MAP: return self.localDisplay.keysym_to_keycode(AK_TO_XK_MAP[char]) elif char.startswith(". import time, logging, threading, traceback import common from iomediator import Key, IoMediator from configmanager import * if common.USING_QT: from qtui.popupmenu import * from PyKDE4.kdecore import i18n else: from gtkui.popupmenu import * from macro import MacroManager import scripting, model logger = logging.getLogger("service") MAX_STACK_LENGTH = 150 def threaded(f): def wrapper(*args): t = threading.Thread(target=f, args=args, name="Phrase-thread") t.setDaemon(False) t.start() wrapper.__name__ = f.__name__ wrapper.__dict__ = f.__dict__ wrapper.__doc__ = f.__doc__ return wrapper def synchronized(lock): """ Synchronization decorator. """ def wrap(f): def new_function(*args, **kw): lock.acquire() try: return f(*args, **kw) finally: lock.release() return new_function return wrap class Service: """ Handles general functionality and dispatching of results down to the correct execution service (phrase or script). """ def __init__(self, app): logger.info("Starting service") self.configManager = app.configManager ConfigManager.SETTINGS[SERVICE_RUNNING] = False self.mediator = None self.app = app self.inputStack = [] self.lastStackState = '' self.lastMenu = None def start(self): self.mediator = IoMediator(self) self.mediator.interface.initialise() self.mediator.interface.start() self.mediator.start() ConfigManager.SETTINGS[SERVICE_RUNNING] = True self.scriptRunner = ScriptRunner(self.mediator, self.app) self.phraseRunner = PhraseRunner(self) scripting.Store.GLOBALS = ConfigManager.SETTINGS[SCRIPT_GLOBALS] logger.info("Service now marked as running") def unpause(self): ConfigManager.SETTINGS[SERVICE_RUNNING] = True logger.info("Unpausing - service now marked as running") def pause(self): ConfigManager.SETTINGS[SERVICE_RUNNING] = False logger.info("Pausing - service now marked as stopped") def is_running(self): return ConfigManager.SETTINGS[SERVICE_RUNNING] def shutdown(self, save=True): logger.info("Service shutting down") if self.mediator is not None: self.mediator.shutdown() if save: save_config(self.configManager) def handle_mouseclick(self, rootX, rootY, relX, relY, button, windowTitle): logger.debug("Received mouse click - resetting buffer") self.inputStack = [] # If we had a menu and receive a mouse click, means we already # hid the menu. Don't need to do it again self.lastMenu = None # Clear last to prevent undo of previous phrase in unexpected places self.phraseRunner.clear_last() def handle_keypress(self, rawKey, modifiers, key, windowName, windowClass): logger.debug("Raw key: %r, modifiers: %r, Key: %s", rawKey, modifiers, key.encode("utf-8")) logger.debug("Window visible title: %r, Window class: %r" % (windowName, windowClass)) self.configManager.lock.acquire() windowInfo = (windowName, windowClass) # Always check global hotkeys for hotkey in self.configManager.globalHotkeys: hotkey.check_hotkey(modifiers, rawKey, windowInfo) if self.__shouldProcess(windowInfo): itemMatch = None menu = None for item in self.configManager.hotKeys: if item.check_hotkey(modifiers, rawKey, windowInfo): itemMatch = item break if itemMatch is not None: if not itemMatch.prompt: logger.info("Matched hotkey phrase/script with prompt=False") else: logger.info("Matched hotkey phrase/script with prompt=True") #menu = PopupMenu(self, [], [itemMatch]) menu = ([], [itemMatch]) else: logger.debug("No phrase/script matched hotkey") for folder in self.configManager.hotKeyFolders: if folder.check_hotkey(modifiers, rawKey, windowInfo): #menu = PopupMenu(self, [folder], []) menu = ([folder], []) if menu is not None: logger.debug("Folder matched hotkey - showing menu") if self.lastMenu is not None: #self.lastMenu.remove_from_desktop() self.app.hide_menu() self.lastStackState = '' self.lastMenu = menu #self.lastMenu.show_on_desktop() self.app.show_popup_menu(*menu) if itemMatch is not None: self.__tryReleaseLock() self.__processItem(itemMatch) ### --- end of hotkey processing --- ### modifierCount = len(modifiers) if modifierCount > 1 or (modifierCount == 1 and Key.SHIFT not in modifiers): self.inputStack = [] self.__tryReleaseLock() return ### --- end of processing if non-printing modifiers are on --- ### if self.__updateStack(key): currentInput = ''.join(self.inputStack) item, menu = self.__checkTextMatches([], self.configManager.abbreviations, currentInput, windowInfo, True) if not item or menu: item, menu = self.__checkTextMatches(self.configManager.allFolders, self.configManager.allItems, currentInput, windowInfo) if item: self.__tryReleaseLock() self.__processItem(item, currentInput) elif menu: if self.lastMenu is not None: #self.lastMenu.remove_from_desktop() self.app.hide_menu() self.lastMenu = menu #self.lastMenu.show_on_desktop() self.app.show_popup_menu(*menu) logger.debug("Input stack at end of handle_keypress: %s", self.inputStack) self.__tryReleaseLock() def __tryReleaseLock(self): try: self.configManager.lock.release() except: logger.debug("Ignored locking error in handle_keypress") def run_folder(self, name): folder = None for f in self.configManager.allFolders: if f.title == name: folder = f if folder is None: raise Exception("No folder found with name '%s'" % name) self.app.show_popup_menu([folder]) def run_phrase(self, name): phrase = self.__findItem(name, model.Phrase, "phrase") self.phraseRunner.execute(phrase) def run_script(self, name): script = self.__findItem(name, model.Script, "script") self.scriptRunner.execute(script) def __findItem(self, name, objType, typeDescription): for item in self.configManager.allItems: if item.description == name and isinstance(item, objType): return item raise Exception("No %s found with name '%s'" % (typeDescription, name)) @threaded def item_selected(self, item): time.sleep(0.25) # wait for window to be active self.lastMenu = None # if an item has been selected, the menu has been hidden self.__processItem(item, self.lastStackState) def calculate_extra_keys(self, buffer): """ Determine extra keys pressed since the given buffer was built """ extraBs = len(self.inputStack) - len(buffer) if extraBs > 0: extraKeys = ''.join(self.inputStack[len(buffer)]) else: extraBs = 0 extraKeys = '' return (extraBs, extraKeys) def __updateStack(self, key): """ Update the input stack in non-hotkey mode, and determine if anything further is needed. @return: True if further action is needed """ #if self.lastMenu is not None: # if not ConfigManager.SETTINGS[MENU_TAKES_FOCUS]: # self.app.hide_menu() # # self.lastMenu = None if key == Key.ENTER: # Special case - map Enter to \n key = '\n' if key == Key.TAB: # Special case - map Tab to \t key = '\t' if key == Key.BACKSPACE: if ConfigManager.SETTINGS[UNDO_USING_BACKSPACE] and self.phraseRunner.can_undo(): self.phraseRunner.undo_expansion() else: # handle backspace by dropping the last saved character self.inputStack = self.inputStack[:-1] return False elif len(key) > 1: # non-simple key self.inputStack = [] self.phraseRunner.clear_last() return False else: # Key is a character self.phraseRunner.clear_last() self.inputStack.append(key) if len(self.inputStack) > MAX_STACK_LENGTH: self.inputStack.pop(0) return True def __checkTextMatches(self, folders, items, buffer, windowInfo, immediate=False): """ Check for an abbreviation/predictive match among the given folder and items (scripts, phrases). @return: a tuple possibly containing an item to execute, or a menu to show """ itemMatches = [] folderMatches = [] for item in items: if item.check_input(buffer, windowInfo): if not item.prompt and immediate: return (item, None) else: itemMatches.append(item) for folder in folders: if folder.check_input(buffer, windowInfo): folderMatches.append(folder) break # There should never be more than one folder match anyway if self.__menuRequired(folderMatches, itemMatches, buffer): self.lastStackState = buffer #return (None, PopupMenu(self, folderMatches, itemMatches)) return (None, (folderMatches, itemMatches)) elif len(itemMatches) == 1: self.lastStackState = buffer return (itemMatches[0], None) else: return (None, None) def __shouldProcess(self, windowInfo): """ Return a boolean indicating whether we should take any action on the keypress """ return windowInfo[0] != "Set Abbreviations" and self.is_running() def __processItem(self, item, buffer=''): self.inputStack = [] self.lastStackState = '' if isinstance(item, model.Phrase): self.phraseRunner.execute(item, buffer) else: self.scriptRunner.execute(item, buffer) def __haveMatch(self, data): folderMatch, itemMatches = data if folder is not None: return True if len(items) > 0: return True return False def __menuRequired(self, folders, items, buffer): """ @return: a boolean indicating whether a menu is needed to allow the user to choose """ if len(folders) > 0: # Folders always need a menu return True if len(items) == 1: return items[0].should_prompt(buffer) elif len(items) > 1: # More than one 'item' (phrase/script) needs a menu return True return False class PhraseRunner: def __init__(self, service): self.service = service self.macroManager = MacroManager(service.scriptRunner.engine) self.lastExpansion = None self.lastPhrase = None self.lastBuffer = None @threaded #@synchronized(iomediator.SEND_LOCK) def execute(self, phrase, buffer=''): mediator = self.service.mediator mediator.interface.begin_send() expansion = phrase.build_phrase(buffer) self.macroManager.process_expansion(expansion) mediator.send_backspace(expansion.backspaces) if phrase.sendMode == model.SendMode.KEYBOARD: mediator.send_string(expansion.string) else: mediator.paste_string(expansion.string, phrase.sendMode) mediator.interface.finish_send() self.lastExpansion = expansion self.lastPhrase = phrase self.lastBuffer = buffer def can_undo(self): if self.lastExpansion is not None: return model.TriggerMode.ABBREVIATION in self.lastPhrase.modes def clear_last(self): self.lastExpansion = None self.lastPhrase = None @synchronized(iomediator.SEND_LOCK) def undo_expansion(self): logger.info("Undoing last abbreviation expansion") replay = self.lastPhrase.get_trigger_chars(self.lastBuffer) logger.debug("Replay string: %s", replay) logger.debug("Erase string: %r", self.lastExpansion.string) mediator = self.service.mediator #mediator.send_right(self.lastExpansion.lefts) mediator.interface.begin_send() mediator.remove_string(self.lastExpansion.string) mediator.send_string(replay) mediator.interface.finish_send() self.clear_last() class ScriptRunner: def __init__(self, mediator, app): self.mediator = mediator self.app = app self.error = '' self.scope = globals() self.scope["keyboard"]= scripting.Keyboard(mediator) self.scope["mouse"]= scripting.Mouse(mediator) self.scope["system"] = scripting.System() self.scope["window"] = scripting.Window(mediator) self.scope["engine"] = scripting.Engine(app.configManager, self) if common.USING_QT: self.scope["dialog"] = scripting.QtDialog() self.scope["clipboard"] = scripting.QtClipboard(app) else: self.scope["dialog"] = scripting.GtkDialog() self.scope["clipboard"] = scripting.GtkClipboard(app) self.engine = self.scope["engine"] @threaded def execute(self, script, buffer=''): logger.debug("Script runner executing: %r", script) scope = self.scope.copy() scope["store"] = script.store backspaces, stringAfter = script.process_buffer(buffer) self.mediator.send_backspace(backspaces) try: exec script.code in scope except Exception, e: logger.exception("Script error") if common.USING_QT: self.error = i18n("Script name: '%1'\n%2", script.description, traceback.format_exc()) self.app.notify_error(i18n("The script '%1' encountered an error", script.description)) else: self.error = _("Script name: '%s'\n%s") % (script.description, traceback.format_exc()) self.app.notify_error(_("The script '%s' encountered an error") % script.description) self.mediator.send_string(stringAfter) def run_subscript(self, script): scope = self.scope.copy() scope["store"] = script.store exec script.code in scope autokey-0.90.4/src/test/iomediatortest.py0000664000175000017500000000163511202265033017444 0ustar chrischrisimport unittest from lib.iomediator import * class IoMediatorTest(unittest.TestCase): def testKeySplitRe(self): result = KEY_SPLIT_RE.split("+y") self.assertEqual(result, ["", "+", "y"]) result = KEY_SPLIT_RE.split("asdf +y asdf ") self.assertEqual(result, ["asdf ", "+", "y asdf "]) result = KEY_SPLIT_RE.split("+y
") self.assertEqual(result, ["", "", "", "+", "y", "
", ""]) result = KEY_SPLIT_RE.split("+8CDATA+8") self.assertEqual(result, ["+", "8CDATA", "+", "8"]) result = KEY_SPLIT_RE.split("y") self.assertEqual(result, ["", "", "y"]) result = KEY_SPLIT_RE.split("TestMore text") self.assertEqual(result, ["Test", "", "More text"]) autokey-0.90.4/src/test/__init__.py0000664000175000017500000000000011120604061016125 0ustar chrischrisautokey-0.90.4/src/test/interfacetest.py0000664000175000017500000000613511120604061017245 0ustar chrischrisimport unittest, time from lib import interface, iomediator class XLibInterfaceTest(unittest.TestCase): def setUp(self): self.service = MockService() self.mediator = iomediator.IoMediator(self.service, iomediator.XLIB_INTERFACE) self.mediator.start() self.interface = self.mediator.interface def testget_event_count(self): self.assertEqual(self.interface.get_event_count('a'), 1) self.assertEqual(self.interface.get_event_count('b'), 1) self.assertEqual(self.interface.get_event_count('c'), 1) self.assertEqual(self.interface.get_event_count('d'), 1) self.assertEqual(self.interface.get_event_count('e'), 1) self.assertEqual(self.interface.get_event_count('f'), 1) self.assertEqual(self.interface.get_event_count('g'), 1) self.assertEqual(self.interface.get_event_count('h'), 1) self.assertEqual(self.interface.get_event_count('i'), 1) self.assertEqual(self.interface.get_event_count('j'), 1) self.assertEqual(self.interface.get_event_count('k'), 1) self.assertEqual(self.interface.get_event_count('l'), 1) self.assertEqual(self.interface.get_event_count('m'), 1) self.assertEqual(self.interface.get_event_count('n'), 1) self.assertEqual(self.interface.get_event_count('o'), 1) self.assertEqual(self.interface.get_event_count('p'), 1) self.assertEqual(self.interface.get_event_count('q'), 1) self.assertEqual(self.interface.get_event_count('r'), 1) self.assertEqual(self.interface.get_event_count('s'), 1) self.assertEqual(self.interface.get_event_count('t'), 1) self.assertEqual(self.interface.get_event_count('u'), 1) self.assertEqual(self.interface.get_event_count('v'), 1) self.assertEqual(self.interface.get_event_count('w'), 1) self.assertEqual(self.interface.get_event_count('x'), 1) self.assertEqual(self.interface.get_event_count('y'), 1) self.assertEqual(self.interface.get_event_count('z'), 1) self.assertEqual(self.interface.get_event_count('A'), 2) self.assertEqual(self.interface.get_event_count('Z'), 2) self.assertEqual(self.interface.get_event_count('1'), 1) self.assertEqual(self.interface.get_event_count('~'), 2) self.assertEqual(self.interface.get_event_count('-'), 1) self.assertEqual(self.interface.get_event_count('_'), 2) self.assertEqual(self.interface.get_event_count('/'), 1) self.assertEqual(self.interface.get_event_count('?'), 2) self.assertEqual(self.interface.get_event_count('\\'), 1) self.assertEqual(self.interface.get_event_count('|'), 2) self.assertEqual(self.interface.get_event_count('\n'), 1) self.assertEqual(self.interface.get_event_count(' '), 1) self.assertEqual(self.interface.get_event_count("/opt/Dialect/PE/Portals"), ) def tearDown(self): self.mediator.pause() class MockService: def handle_keypress(self, key): self.key = key autokey-0.90.4/src/test/phrasetest.py0000664000175000017500000003006011200545641016570 0ustar chrischrisimport re, unittest from lib.phrase import * class PhraseTest(unittest.TestCase): def setUp(self): # Set up global settings """globals = { WORD_CHARS_REGEX_OPTION : re.compile('[\w]', re.UNICODE), IMMEDIATE_OPTION : False, IGNORE_CASE_OPTION : False, MATCH_CASE_OPTION : False, BACKSPACE_OPTION : True, OMIT_TRIGGER_OPTION : False, TRIGGER_INSIDE_OPTION : False }""" self.defaultPhrase = Phrase("xp@", "expansion@autokey.com") self.defaultPhrase.set_abbreviation("xp@") self.defaultPhrase.set_modes([PhraseMode.ABBREVIATION]) self.defaultFolder = PhraseFolder("Folder") self.defaultFolder.add_phrase(self.defaultPhrase) def testImmediateOption(self): # Test default setting (false) self.assertEqual(self.defaultPhrase.check_input("xp@", ""), False) self.assertEqual(self.defaultPhrase.calculate_input("xp@ "), 4) # Test true setting phrase = Phrase("xp@", "expansion@autokey.com") phrase.immediate = True phrase.set_abbreviation("xp@") phrase.set_modes([PhraseMode.ABBREVIATION]) self.defaultFolder.add_phrase(phrase) self.assertEqual(phrase.check_input("xp@", ""), True) result = phrase.build_phrase("xp@") self.assertEqual(result.string, "expansion@autokey.com") self.assertEqual(result.backspaces, 3) self.assertEqual(phrase.calculate_input("xp@"), 3) def testIgnoreCaseOption(self): # Test default setting (false) self.assertEqual(self.defaultPhrase.check_input("XP@ ", ""), False) # Test true setting phrase = Phrase("xp@", "expansion@autokey.com") phrase.ignoreCase = True phrase.set_abbreviation("xp@") phrase.set_modes([PhraseMode.ABBREVIATION]) self.defaultFolder.add_phrase(phrase) self.assertEqual(phrase.check_input("XP@ ", ""), True) result = phrase.build_phrase("XP@ ") self.assertEqual(result.string, "expansion@autokey.com ") def testMatchCaseOption(self): phrase = Phrase("xp@", "expansion@autokey.com") phrase.ignoreCase = True phrase.matchCase = True phrase.set_abbreviation("xp@") phrase.set_modes([PhraseMode.ABBREVIATION]) self.defaultFolder.add_phrase(phrase) result = phrase.build_phrase("asdf XP@ ") self.assertEqual(result.string, "EXPANSION@AUTOKEY.COM ") result = phrase.build_phrase("ASDF Xp@ ") self.assertEqual(result.string, "Expansion@autokey.com ") result = phrase.build_phrase("Asdf xp@ ") self.assertEqual(result.string, "expansion@autokey.com ") def testBackspaceOption(self): # Test default setting (true) result = self.defaultPhrase.build_phrase("xp@ ") self.assertEqual(result.backspaces, 4) # Test false setting phrase = Phrase("xp@", "expansion@autokey.com") phrase.backspace = False phrase.set_abbreviation("xp@") phrase.set_modes([PhraseMode.ABBREVIATION]) self.defaultFolder.add_phrase(phrase) result = phrase.build_phrase("xp@ ") self.assertEqual(result.backspaces, 1) def testOmitTriggerOption(self): # Test default setting (false) result = self.defaultPhrase.build_phrase("xp@\n") self.assertEqual(result.string, "expansion@autokey.com\n") # Test true setting phrase = Phrase("xp@", "expansion@autokey.com") phrase.omitTrigger = True phrase.set_abbreviation("xp@") phrase.set_modes([PhraseMode.ABBREVIATION]) self.defaultFolder.add_phrase(phrase) result = phrase.build_phrase("xp@.") self.assertEqual(result.string, "expansion@autokey.com") self.assertEqual(result.backspaces, 4) def testTriggerInsideOption(self): # Test default setting (false) self.assertEqual(self.defaultPhrase.check_input("asdfxp@\n", ""), False) # when separated by a non-word char, should still trigger self.assertEqual(self.defaultPhrase.check_input("asdf.xp@ ", ""), True) # Test true setting phrase = Phrase("xp@", "expansion@autokey.com") phrase.triggerInside = True phrase.set_abbreviation("xp@") phrase.set_modes([PhraseMode.ABBREVIATION]) self.defaultFolder.add_phrase(phrase) self.assertEqual(phrase.check_input("asdfxp@.", ""), True) result = phrase.build_phrase("asdfxp@.") self.assertEqual(result.string, "expansion@autokey.com.") def testLefts(self): #Test with omit trigger false phrase = Phrase("Positioning Phrase", "[udc]$(cursor )[/udc]") phrase.set_abbreviation("udc") phrase.set_modes([PhraseMode.ABBREVIATION]) self.defaultFolder.add_phrase(phrase) result = phrase.build_phrase("udc ") phrase.parsePositionTokens(result) self.assertEqual(result.lefts, 7) #Test with omit trigger true phrase.omitTrigger = True result = phrase.build_phrase("udc ") phrase.parsePositionTokens(result) self.assertEqual(result.lefts, 6) #Test with immediate true phrase.omitTrigger = False phrase.immediate = True result = phrase.build_phrase("udc") phrase.parsePositionTokens(result) self.assertEqual(result.lefts, 6) def testLeftsWithSpecialKey(self): phrase = Phrase("Positioning Phrase", "[udc]$(cursor )[/udc]") phrase.set_abbreviation("udc") phrase.set_modes([PhraseMode.ABBREVIATION]) self.defaultFolder.add_phrase(phrase) result = phrase.build_phrase("udc ") phrase.parsePositionTokens(result) self.assertEqual(result.lefts, 7) def testMultipleAbbrs(self): phrase = Phrase("Some abbr", "Some abbr") phrase.set_abbreviation("sdf") phrase.set_modes([PhraseMode.ABBREVIATION]) self.defaultFolder.add_phrase(phrase) input = "fgh xp@asdf sdf " # Abbreviation should not trigger self.assertEqual(self.defaultPhrase.check_input(input, ""), False) self.assertEqual(phrase.check_input(input, ""), True) def testSort(self): phrase1 = Phrase("abbr1", "Some abbr") phrase2 = Phrase("abbr2", "Some abbr") self.defaultFolder.add_phrase(phrase1) self.defaultFolder.add_phrase(phrase2) phrases = [phrase1, phrase2] phrase1.usageCount = 0 phrase2.usageCount = 1 phrases.sort(reverse=True) self.assertEqual(phrases[0].description, "abbr2") self.assertEqual(phrases[1].description, "abbr1") def testNoneMode(self): phrase = Phrase("Test Phrase", "Testing") folder = PhraseFolder("Folder") folder.set_abbreviation("asdf") folder.set_modes([PhraseMode.ABBREVIATION]) folder.add_phrase(phrase) result = phrase.build_phrase("asdf ") self.assertEqual(result.backspaces, 5) self.assertEqual(result.string, "Testing") self.assertEqual(phrase.calculate_input("asdf "), 5) self.assertEqual(phrase.should_prompt("asdf "), False) def testWindowName(self): phrase = Phrase("Some abbr", "Some abbr") phrase.set_window_titles(".*Eclipse.*") phrase.set_abbreviation("sdf") phrase.set_modes([PhraseMode.ABBREVIATION]) self.defaultFolder.add_phrase(phrase) self.assertEqual(phrase.check_input("sdf ", "blah - Eclipse Platform"), True) class PredictivePhraseTest(unittest.TestCase): def setUp(self): self.phrase = Phrase("blah", "This is a test phrase") self.phrase.set_modes([PhraseMode.PREDICTIVE]) folder = PhraseFolder("Folder") folder.add_phrase(self.phrase) def testPredict(self): self.assertEqual(self.phrase.check_input("This ", ""), True) self.assertEqual(self.phrase.check_input("This", ""), False) self.assertEqual(self.phrase.check_input("This i", ""), False) self.assertEqual(self.phrase.check_input("I don't have a problem. This ", ""), True) def testBuildPhrase(self): result = self.phrase.build_phrase("This ") self.assertEqual(result.backspaces, 0) self.assertEqual(result.string, "is a test phrase") def testCalcInput(self): self.assertEqual(self.phrase.calculate_input("This "), 5) def testShouldPrompt(self): self.assertEqual(self.phrase.should_prompt("This "), True) class HotkeyPhrasetest(unittest.TestCase): def setUp(self): self.phrase = Phrase("blah", "This is a test phrase") self.phrase.set_modes([PhraseMode.HOTKEY]) self.phrase.set_hotkey(["A", "B"], "n") folder = PhraseFolder("Folder") folder.add_phrase(self.phrase) def testHotkey(self): result = self.phrase.check_hotkey(["A", "B"], "n", "") self.assertEqual(result, True) result = self.phrase.check_hotkey(["B"], "n", "") self.assertEqual(result, False) result = self.phrase.check_hotkey(["A", "B"], "a", "") self.assertEqual(result, False) def testBuildPhrase(self): result = self.phrase.build_phrase("") self.assertEqual(result.string, "This is a test phrase") def testCalcInput(self): self.assertEqual(self.phrase.calculate_input(''), 3) class PhraseFolderTest(unittest.TestCase): def setUp(self): self.folder = PhraseFolder("Folder") self.folder.set_abbreviation("sdf") self.folder.set_modes([PhraseMode.ABBREVIATION]) def testCheckInput(self): self.assertEqual(self.folder.check_input("sdf ", ""), True) class E2ETest(unittest.TestCase): def setUp(self): self.topFolder = PhraseFolder("Top Folder") self.topFolder.set_abbreviation("top1") self.topFolder.set_modes([PhraseMode.ABBREVIATION]) self.bottomFolder = PhraseFolder("Bottom Folder") self.bottomFolder.set_abbreviation("bottom1") self.bottomFolder.set_modes([PhraseMode.ABBREVIATION]) self.topFolder.add_folder(self.bottomFolder) self.phrase = Phrase("blah", "The Phrase") self.phrase.set_abbreviation("asdf") self.phrase.set_hotkey(["A"], "n") self.phrase.set_modes([PhraseMode.ABBREVIATION, PhraseMode.HOTKEY, PhraseMode.PREDICTIVE]) self.bottomFolder.add_phrase(self.phrase) def testCheckInput(self): self.assertEqual(self.topFolder.check_input("top1 ", ""), True) self.assertEqual(self.bottomFolder.check_input("bottom1 ", ""), True) self.assertEqual(self.phrase.check_input("asdf ", ""), True) self.assertEqual(self.phrase.check_input("The P", ""), True) self.assertEqual(self.phrase.check_hotkey(["A"], "n", ""), True) def testBuildPhraseDirect(self): result = self.phrase.build_phrase("asdf ") self.assertEqual(result.string, "The Phrase ") self.assertEqual(result.backspaces, 5) result = self.phrase.build_phrase("The P") self.assertEqual(result.string, "hrase") self.assertEqual(result.backspaces, 0) result = self.phrase.build_phrase("") self.assertEqual(result.string, "The Phrase") self.assertEqual(result.backspaces, 0) def testBackspacesIndirect(self): result = self.phrase.build_phrase("top1 ") self.assertEqual(result.backspaces, 5) result = self.phrase.build_phrase("bottom1 ") self.assertEqual(result.backspaces, 8) autokey-0.90.4/src/test/configurationmanagertest.py0000664000175000017500000000322111120604061021500 0ustar chrischrisimport unittest import lib.configurationmanager as conf from lib.phrase import * CONFIG_FILE = "../../config/abbr.ini" class LegacyImporterTest(unittest.TestCase): def setUp(self): self.importer = conf.LegacyImporter() self.importer.load_config(CONFIG_FILE) def testGlobalSettings(self): # Test old global defaults using a phrase that has no custom options defined # Locate otoh phrase otohPhrase = None for phrase in self.importer.phrases: if phrase.abbreviation == "otoh": otohPhrase = phrase break self.assert_(otohPhrase is not None) self.assertEqual(otohPhrase.immediate, False) self.assertEqual(otohPhrase.ignoreCase, False) self.assertEqual(otohPhrase.matchCase, False) self.assertEqual(otohPhrase.backspace, True) self.assertEqual(otohPhrase.omitTrigger, False) self.assertEqual(otohPhrase.triggerInside, False) def testPhraseCount(self): self.assertEqual(len(self.importer.phrases), 23) def testPhrase(self): # Locate brb phrase brbPhrase = None for phrase in self.importer.phrases: if phrase.abbreviation == "brb": brbPhrase = phrase break self.assert_(brbPhrase is not None) self.assertEqual(brbPhrase.phrase, "be right back") self.assertEqual(brbPhrase.description, "be right back") self.assertEqual(brbPhrase.mode, PhraseMode.ABBREVIATION) self.assertEqual(brbPhrase.immediate, True) autokey-0.90.4/config/autokey-qt.desktop0000664000175000017500000000037211746515472017252 0ustar chrischris# $Id: autokey-qt.desktop 22 2008-01-29 11:29:13Z peabody $ [Desktop Entry] Name=AutoKey (KDE) GenericName=Keyboard Automation Comment=Program keyboard shortcuts Exec=autokey-qt Terminal=false Type=Application Icon=autokey Categories=KDE;Qt;Utility; autokey-0.90.4/config/autokey-gtk.desktop0000664000175000017500000000037411746515472017415 0ustar chrischris# $Id: autokey-gtk.desktop 22 2008-01-29 11:29:13Z peabody $ [Desktop Entry] Name=AutoKey GenericName=Keyboard Automation Comment=Program keyboard shortcuts Exec=autokey-gtk -c Terminal=false Type=Application Icon=autokey Categories=GNOME;GTK;Utility; autokey-0.90.4/config/autokey.svg0000664000175000017500000003221711636471517015760 0ustar chrischris image/svg+xml autokey-0.90.4/config/autokey-status-dark.svg0000664000175000017500000001105711661306541020207 0ustar chrischris image/svg+xml autokey-0.90.4/config/autokey-status.svg0000664000175000017500000000727411636471517017306 0ustar chrischris image/svg+xml autokey-0.90.4/config/autokeyui.rc0000664000175000017500000000524111726636462016122 0ustar chrischris Settings Help ToolBar autokey-0.90.4/config/autokey.png0000664000175000017500000001730311710765364015743 0ustar chrischris‰PNG  IHDR\[þêâàsBIT|dˆ pHYs × ×B(›xtEXtSoftwarewww.inkscape.org›î<@IDATxœí][\ÇYþþ>3³7­÷*íE«•%Y²eK–Û¹*ŠK(Š*.„¢xò@ÁE¨ðÀ[¨ð„2JBBÅP±ˆeËÙ‘"˱ÇÉ’%­våÝÕ^fºúÿ»ûÜfF¨¸·zçÌ™3çtçïïÿúï>=ÄÌ(&"€L²‘W*øNJ°œ¼ÚJl‹;‰ÈÐÐ@þÀ»'ÜèØ°ÅÌ.=¨¡bÕMx° }ñø™Oí™ÛõÁñƒÓÍFóÄ«ØjwÚ+k›W_¿xùø/~ðè'lȈh @[ ;X85 úø'þìžÿöÇÿnfrÇô§ ÿ¿Ó›×Ö®~æ¯?óëŸùÔŸ<ü fn¸ÐȈäé§_ºôÙÉÑ©¶uXßì m\Ýlú¿ÚB~ðš†ÍÌ`d°ffpéÚêÒ}g? à*€uëÌìŒPÉ€äÑÏ>|⓳“£Së›,­na³maƒ}gÔäpLý!o+;æ¾ré»ý–·lc³m±´º…õÍf'G§>ûð‰OU|‰ xo0¾wÏüû·Û«훺Ã%ëífÎ? IUyÿ·ó=1¶šƒóU¸™+®n´ÑÌ öî™?€q›ÚÚ xÙ×€§“‰©±Ñ±õ­NßÐBqúæ(Õ-ÖÛ-NüWÍôÀÊÍ®i}«ƒ©±Ñ1VÜ`RÙ×04<ÐÌ®­oWžzyu_>~¯¿¹ŒõÍ-€´\”l£²•÷âfoKÆ(ìèž‚uPî-ˆJ§Ho.'ÿt{dp{f&ð±ÅÄèPéRmk1:ÒÊà™£ÁY-<ƒ—„ƒ À¹j°ÿò¡¯£íÖ Z;†B\JjA)šôv?oZœÛHÀÏY~º]¸*QÎ@Ò’2!×L9¹™zFNnøV§ƒ^¿Šï=ôuüÁ¯ýD tçÂá±Í žÞ¬«ú—ŸÆ¶% îGÖj‘:äÁÖw”Ç»ƒúT;Íêé ûý±É÷B!´R(¨/wü<7Éi¢EAwì5±¹¶‚/?ßøÙê*¢¸ÀÓDÁkÒ«—–Ñh k6ÐÈ 2 µ˜Ä`ª­=œ¾®P…$–ÄÉ{ ™Á$¯Hß dŠ\®$`G ‰Ò÷´}’XzJcª°˜aØMF£9€W/-Wb¦."ÝWx5â7nl¢9:(¶@áŒL1¢51Ë&ùúç.WQ‚Êäe(…R+ØNt˜‹à;¯‹ )ÈÆ€ÈF@5xßñ7Ž’2¤è¥ É· 52ÜX]­ÄLÊÓðKɉT<è%Ö²I… åó„&C{§”J $³‹ÙÅWŸÙ%”ƒ\YBkd# aL¤NZfŠÀ‚iSPeÏÔ[%i*^õE‚p 'Û{®VëPw£OéIÅjº§H%¤• –ígçÀÎJNÀO¬>Ü[3‘šŒ¹ &Ë6€Éü5É€ÈHà[+ ñÔ‰ú:Š]ùúW`WUÛðÈU|$kO1g J-›rk©xÑYÅžžl[çn›náØâ(Ø)‡kÓŽ4ÖÈÄ×Ï¼Ë«Û “ÁbÊ0zYãs°øÀ æÔäóÊEƒµ¾Õ)õÎpÝY×*IŠä¬¦Ë¡‚þµJ)Á-ï˃-ÛY ç:`ÛÁûÍàç~âÁÚÚS»ÝÆååÃ×¾õLÖgŒ& @Æh=)øÉQGp(Z>Ý'«÷¤hå ‡Ð˔©c 8j3÷Xj3”‚yK¯_ì@TÒxª¢ìcö´áØÂÚ6N·pÏûºÀ[NÍfÓ£ƒè´7a\†-2 eœÁÆo³÷9ÌbÁZWòô)e'.5°ªŽqI¥Ô¶ 84"D)¦^ÞïS^×3&/9/_<=E‡È`kkÁÞsp ó3µàÖ¥ñÑAØ­M áç|œ†ÔZËî Td”äb±ƒóû(´È:>)›XW§Y4F®L>§üÁœìÓKumM¡Bk V†sNr‹M»ãÖšstOã;Fà:[¾³â.äMš2€Æ0ˆX%$bkÍqÜ%çë•ÚÓ Nªê ¡WÄzï…Ë“ '–Mœê’PÍBJ¹;Z·W!ž·íàƒ‡°oqw5¢=ÒîÙ˜Îpeu+¸«z\$#D›0€Z8‰c hk‹Ž4÷ªÇFxÿœ’º ¦VÌið7yï%Q,%Ô“~‡ríC³ËßsªóÜÍ^ú9×Áü-Gí}[`À®SØ?7 ÛÞ„ëlÁv¶álÎvÀ®° 7¢ç™Èq˜Ü ÂûfeŒM±p5_…ºRßÉ@Ü›sry©Vú¬";—tj‚e‹2±Îvð¡Ãs8¸ñm><<„}óÓè´·ÑioöÛp6¬€n­Þ`²¯FNÈõdÕqª¼¨ÜåS…Ó,l„KäORu²*ñ_ŸÊÜo„ƒcë,&‡3¹m¡û`ADD˜í´½Ï°¶rÎYs0ÎÁIŒÆjYÕ6Qñ>\¯ËÛ YX ¥÷ìI³JOÇ] ®ý¨ÌÝp^E°X6Û>rd‡ÕKÁÍÍM Ö~®ibl®³ g2¸¬—5ÁYÇëòÌÓ °; v™‚*H¨ ^­”+Z…eJ)‰Ó,ó”6ŸH+u¹@'•Ǹ¸íd¼Ñ98±8ç<Œ¶wØ cJÌÒɧNÕI>ÍLObÇ@S(Ä;b½[ 'Tâœ/›c†ÕT”¹:ÌRlSÀs½¡ê¤§[ýü9î¶ßùÌN_èüäÝs8zø¶Ú]¹º„¿÷*666{¾ga ³“p®“»†c ÇV®Äe´l]ÿRö®M9l‹¦£1ª*¼óÛ7‘ëœxøÌ1تô‘á®ýsȲ¬¶&<…Ï}å\¾rµ[…c·Œâ®{ÀÖS; ¶ßf,º  rïKW¦Ìƒ‚—¯Å¦¬>n&Çé .¾:ß¹±,Ýwç`E5|äèî=r{-€«kkøÆãOãÂÅ7ñúù7zÞh40·s6¥-§×Ô÷)è1ì[I'2µÅת”îN3ÑåÝ%yôvR,$„»µ9;©x+cܵoFe¸ðاð¯ßxíí6––—ûºòäøXàig=}…0/GIÈÆÙx'®Ì©¿,øªz‹/ãT¼ê-îºÎ(ÜÖb¤ä©þ†4s¶|äžYÜwìŽÚóommã‰S§a­,]»Þ£¼>MOŽ!3h$º;«Cd©)IQ­YEöWÙe±§Y ÏæNÇÿ Ùù™RÁJ\Œ™°uhptßZ­V-pO<ý,¾ðÕãòŽpùê58çj×´¸{3“·øÎVˆÓ¤ Gú‹3Ä‘½º\s½Šð,e¡bTç>xºþó‚Le˜È3ë:øð»pÿ½‡kAët:xê¹ç±±¹ ÀGõξô}¬­­÷|çÎ)ÜyÛbT$zmáïx¢\ueb”ˆEyXËß9\J§Éuh—ok—ÛÎUûÅ©ÉpèèøŠglqìÀl׎̩oÅ?>üÜÐÙ˯¿×/ôvœCCC¸mÏ\ÂÙÅî|¤ßëL»|ö·¢»JAð%ró®b+tD™øìµñïÂ÷ÞY ˜s§ÎœÃõÕuÝ“qÊ+ËoáÒ›WzSc«]:0­–¬œ–½·&¯A¼”JCl“®äFDËíªRJáâA™¨:ÑÞ¥u€ëà]·Íbdd¸öÔgϽˆÏ}åÑJ•ù(ÌÀÒò[õeJÒÔÄ-9EéÄS œ©R±þ¦ÂpÀ.¯T ¯H‚eí[:tPJ\"¨<²•8Üs ZmÊÒùxÏÁiÜO=w33ž=s¯,Ë´‡ügË×Wk¿›¦™ÓÂêVB%ÎyºsJ{òjdd(I™>êí/|Ð…Ãkœ@ .4¦Âûª†ç¤©2œt|4{«b¶¸ÿöyŒÝR Ô˯¼†/|í„ØŠÒIÊ|ôïÂ嫨Þî=Åzqa·îÞ•£Ç–m(—†\òÚ­îÿË"=8<±ò|vÚŒôB}å¨LBOSš¯Ueb;x×¾ ܬ¾W O?w/¼rÞ +“°É4@Yg¿{ׯ¯ô|ttŽºUzºEihºQꋯ%¥­[5fyL«/SGB!ÚÔº‹ÒÂ1ªLÒÎE"Áï;¼ÓS“µ ]¼t?ú¼ $ë–Ø¶É¸pu¯ž¿Ôð,Ë03=‘tzòJEËxœ^W­ºÙÕÔô4ËI•cÀŽa 71Èorx£ÀI‡ƒ¥k}d÷î»ûPWN>sOžyÑS‰X¸1錩õÍ®^ëmá05>–8nF®ã#¯–†Ø‡ÄËàr¬_°«˜“LHzš5N³( {§ e¢óAT:‹]ÀÜÌÎÚ³¬\ ÿqò9oIÆíh%™síúZåvN#3:8%¢c “tïC]™uN„Ö,0wdïjÄУ§YûåTð*;;©·WU¢¸N‡f†ñî#»‚sâÉSø÷ÇNIý X“P‰ÏMÓÀµ>•ÊâÂ<æ¦ÇbYdv@œ³hÃè“K:?¬¶tc7ïK¥¸¨1ìBÁâÀ‚ò¶æÜs+æjÙØØÀ‰g΢#Aª`(¹9Þ&¼~ÿÂlllô|zjGî-s·l]TR¬ÛÉHPm¿˜]ìð„RêSÑ/TÅäSÏ >U*ü½ÓC¸÷ðþ®À<þÔ³øÒ#A¥j„ ºd–×ï¼v —¯,õ|ppû÷Ìåzš©$Ì «s†Üm¢ /ä)¥BÖ4Õ•7# ]b!Þm;pÖâ§ß½öí©-l»ÝÆSßz›[9y™zTQ13.^½Žóßì ¶¦é‰$6žZxùQÅ’o©uÁ«jÐÊ:¼bª[—{–r4 r¤tLœü(^Ÿ•ý€íì-~Rf·©O:Ï}å°sþ8“HNã+JìbiÙa{Ûbi¹ÿØxN® Ø&+ÄÆ|è¯S£® ,kž€(ÈyÒ¨»Bþ8µŽ4k;ø¹ûᎃõS¬µ8uæ,¯¬ {èè‹óÓМMžÍñeÑk,­ôç8gwíÄÄè0®o´a$Žbd09Ò ‹j1¢T"…pøŸ*·¼’© v”âᮚ˜ |\ÙîKû pÒšiâž;níjݧ¿}ÿÏ_G[vj.Ì])çËWWúŒX˜ÇÅ9‰Í'½ÉôâøBœ?÷<ç°¨ÂÌE§*[=¦Y•Š×Ã` øplš.Ÿý‡qäŽú©°¶¶†ßÿ­_FÊYÚÁIŸHË[/S«‘akkCCÝ'íØ1‚£oÅÓg¿_)C&v>2ÉZGeë…G”"§­˜ãZpåór<ðÜÈb‰¢wG÷ܾØub|à}ý?áðv“13Ó¥®½c™öÆp"g ô•è´kðÐÏ£¶ ©ö ˆêÔ­§©{ÐK&ØxÐ?öàí8vW÷nüÿfšž÷‡Áçù\zš¤Ž3ˆ`À!<Ùðv%‡WÀTDî‚uðÆA¨ú9'ÐØ„unî9´ÐuêÃÿvÚ95V–ILœsñ$CwW r}­JÑsÖ"»pÒBN¬;¶TÍ8mžV{—®ƒŸ¿?î;V?|öÃHûwcaf"ñ3ÑÒc=8ô8K![‰þ‹<+;ͨûÍ-Œ‚'Ÿ¸òÏ!½x:ÈàÁ¶Îb ¼ëö=h6kåÿ¡¤‰‰qÜ}Ço)0' áBý\ÍØf­±Ô±…C*[ÚœrN‚Ü6'Þ]­Ævð3÷ïÇý÷Þõó?‘°oa6(ª4FŒ 'Ú_µ¸ÖǤ"Uí-ªqüFq H¹:œU¥`lna3ç,ĸïö=¨­¸s®¯°7“†‡‡úšÄ?=9^P(Jƒ.(;oÙH\œÁ0HÙ7:´´båë•d¡ÂcÍ9;®áœøk vt kñS÷íŃï:ÒµÒß:ó<þêoþ ÛítLR?$Y|@^ý%Z˜V6 %,ÎïÄþÎoâ–[F»^ð±ñ#‘‘²¿ þÜ$À?¿ÉàÒ PEð§è% }¬ « ©P" ‰t$è¼ß=;ƒÉ±Q¬ÜØ”Xœgèã9N¨“ä©ìT Öp8—M¼¤RêŽzÝè¥ ‘³dn‡“ Õ[À{zX÷‹ß}ýË#¥‘xÊš>7š0VMöŸùcš~ "kàêõ5\ìsbÐîùYܱ7tÊ[Œß'QÃJëë_½f¯…,àªû*,<94Ižj’ž3˜¤8Ð&¹sï?r+FGwt­ðÉgÎà{¯]ÌQ™8H‹ÕÂõáx -"DýLFq®­ô5Æ‘Cûpâ¹—‚¬%g}§'íy’µr#þLšbVÕϧþ{!¹NOÙiÆ!5_ÐÞ÷¼»»2¹ðÆE|ñ«ÇCl„Èä¦?!CjËðØÂØY0Y„¥•þÆ71˜ž¿v‚¼aÁpò´‚®¾ï+ ©8ˆlê:îÅŽO”E,œE0,~üÞý˜ïzñÇž|Ïœ})Ö‰=M¿¼FÖÈ®CiÑq&üd ›K+°Öv}dEÓôäœ<‘¦} …Et!±p2%^Ê1zó¥¹…¹oäNâMœûywðã 1]á=8‹;0ƒ»L9€ååëøêñ'eI$Y,&ó“z”»I7Yæ·•vÒbŠe’óÏäƒ: "<ÿòX[[ÃØØXOÀg¦§0Øl`»è£´»/ÊËÃ8CQQ/¨”$uïøT§T*!ryñ3ê¾ص³û:ÁÿùÄ3xäñg –-#ðâIœ ~–ãðP4Ç‚œ#oP¯]^Áëo¼Ùà{ç±8· ß½p5Wϰ¢EP)bÑì<~a  ‚¿kR÷8iZ/Íì)Ëéè>XÊèÌÝ·NácÝ­ûÆ <úäsp JUB¨Ñ‚i¶@ÍLcÀ¿Oö›f+—Ïþ†­ÜèàÒåÞÊ01>†{ï:jÊ"s£Îo;.ÇñúMK0Nüƒw$ÎÈrªf869ç~æCØÝcM“ož<…/}ý$â<ÁF˜#HÂßx¼)7%RJ”@Òê²R,îbP«ÕÂâÜL0Ú4úé˜aÖn¾ Œ­ôÌJÈs‚kðˆ{¹gïcÜJ!NŸ>G¸ ;‡ÛçÇñcG»Oìi·Û8ùìY´;&Ûd £¹Þ“cŒª”´üžsý2!q)gÈdXºÞû1M»¦&ý›Ö>:eJ^=gÇØ '¦žÇ¼Z¨\Ü ª‰gZiüÄsŒ3ãÞ{û÷.t­Ø“§Nãþí1ï$Éâa~eÖ¬d™ÿÜÈ~“ù¥‘HÖL­@ûº°,6ãÓÀÅ¥ëèt,ÞJeçÔx®)}†0†ó+97®r4§Å+±«œTýišäî9Q&Ë%/îÜûîênÝÎ9 M‰(vŠýµƒn&›Fjör·$ä\¸Ü ¦ †2°ün”§R‘8èjœ*kQ+a[¢¿ØcEû&凌ìJó¹Ütu´ÌƒEÌ&·h$I  àðR­Àz±çe¶@I…2AŠM;ìy=Õ—L›ËC DÞ)“PiÓƒ)—Z©óœi}5UëðŠäÒŠÁ& ÂŒ–ÀŽMy&4‹PÉØì 8¨ãc+n9°Ikä¯Aè0ç’lŒ”P}É3˜ÉDPFtò _†ÐÊÂêÑþx&ŠS™ë ¼^£p02ÜÂê†Qæ»ä¸°Ö¶§™º,šËì79ñjŠ·°5_±(”HhDAbÝ©WO€ÖLr=)Âiý9“Véòú@-I«P: ›ñ Àß`k±c¸Ué3¯aµù|Ú??ç^¼g2?æg"ˆ¡é'1eJLŽXŇKøŠp2ˆê‚µ1Ø `K‹¨Ka BZ§eIC£A­?b–áVº Fx:þ••bÃÁÏJØ??S£R8ž¼ðè…*êö«}/Î}ÿóØÜÞ ½>ò¸~‘Øéœ»üúœªn]-jäøH‰+6‹:ă‰³–%Çër’ý©nÖ#p €,Tó;-«úQ;‘²œÃ`ËàW?úÞÚbå°EMxVm1MSã#øÓßù%<ôÕxùüU¬mÜ9)Eº+0ƒ“÷i RàDCÅÎ oD(j7°“ssRv½Ù Ðök'.ÃÏTËL0 ´¶ 'è0:4Œý Óøµ¾Sã#(š8•öøT|åúªœk€Ë‡Oà÷~å'ó½´P!äâ,œ¼*þŸ |T€œþ†Ç9ÝkŽ çh±…¥ñŽ´¬I«—*–ª^Â<ë]aeå-[ÜÝ@ t´_;aczr|ÔÖ.+!–Àœ¼ †*µ—Žl€\YÂ÷€j8V®Âòe‚ÐY¹â¾æwEGúƒ"¤ïBë¢øy÷b„d áµó6à¬ÁÙÈ›6üï…Ýxêä‰óúa»Ê¬=4ÿ^ÈM<½—u&p=‰"Ég¯Ò_! *úl—ªô•ÓcÓó¡² YEV‰jB]bˆ*ê]¬è)XÞ@ü-¶ŽAü•Ó-}úÏ¿ùís/Ý0†ÐÈÈKÓ.‹ëy+H¼å³ïÂ˲#’‰bSÍ .•çaeõ›ÈéwóåÐmÊ•G3LR&“–«X×*Æ@0#|ûÜK7úôŸþ÷4·g‚ÿ°)3¸svÏþÿøSŸþÐ݇)ß*˜±©)÷F • ä¨D`6€ÚÏ@H~ýªð"gæ|(Ç[¸Û¿ñ0H/8òvü,¥îñYÊhœ^KŽŸù>÷ÒÆ'?ñ»^zýå'<à%oXÊy b@cí­e~øóŸ]Z·­aÓn´Z¦Õl.á¡!9—nû º¤²þASäa}J9¥•Ôý:Ã+\/8=ÄéѺß%‹N².¢Ÿ!yE(¯c–§¶ô})sRn=ƺü¶jpÎâÚÊ[öùï|oã ŸÿüÅ¿ø£ß=¾öÖòw|À+ð?bº `]16ƒÿÂi‹vØ `^öÀÿžX?úìG11içQóv¶ÿ €æÌˆÌo¾IEND®B`‚autokey-0.90.4/config/autokey-status-error.svg0000664000175000017500000001111311662660522020413 0ustar chrischris image/svg+xml autokey-0.90.4/uic/centralwidget.ui0000664000175000017500000000776011725514530016261 0ustar chrischris CentralWidget 0 0 832 590 Form Qt::Horizontal 1 0 Qt::CustomContextMenu true QAbstractItemView::InternalMove QAbstractItemView::ExtendedSelection true false 3 true Name Abbr. Hotkey 2 0 1 0 0 Qt::ActionsContextMenu false true QAbstractItemView::NoSelection true FolderPage QWidget
configwindow.h
1
PhrasePage QWidget
configwindow.h
1
ScriptPage QWidget
configwindow.h
1
AkTreeWidget QTreeWidget
configwindow.h
autokey-0.90.4/uic/generalsettings.ui0000664000175000017500000000473111723375735016630 0ustar chrischris Form 0 0 491 444 Form Application Prompt for unsaved changes Show a notification icon Popup Menu Allow keyboard navigation of popup menu Sort menu items with most frequently used first Expansions Enable undo by pressing backspace Qt::Vertical 20 40 autokey-0.90.4/uic/recorddialog.ui0000664000175000017500000000334511723375735016070 0ustar chrischris Form 0 0 400 300 Form Record a keyboard/mouse macro Record keyboard events Record mouse events (experimental) Start recording after 5 seconds KIntSpinBox QSpinBox
knuminput.h
autokey-0.90.4/uic/windowfiltersettings.ui0000664000175000017500000000462011726560370017717 0ustar chrischris Form 0 0 425 120 Form Regular expression to match: Window title Enter a regular expression that matches the title of windows in which you want this item to trigger. false true Apply recursively to subfolders and items Qt::Horizontal 40 20 Detect Window Properties KLineEdit QLineEdit
klineedit.h
KSeparator QFrame
kseparator.h
autokey-0.90.4/uic/phrasepage.ui0000664000175000017500000000625611723375735015555 0ustar chrischris PhrasePage 0 0 540 421 Form Qt::AlignCenter Open the phrase in the default text editor true QTextEdit::NoWrap false Phrase Settings Always prompt before pasting this phrase Show in notification icon menu Paste using Qt::Horizontal 40 20 KSeparator QFrame
kseparator.h
KUrlLabel QLabel
kurllabel.h
KTextEdit QTextEdit
ktextedit.h
SettingsWidget QWidget
configwindow.h
1
autokey-0.90.4/uic/scriptpage.ui0000664000175000017500000000457211723375735015576 0ustar chrischris ScriptPage 0 0 587 581 Form Qt::AlignCenter Open the script in the default text editor Script Settings Always prompt before running this script Show in notification icon menu KSeparator QFrame
kseparator.h
KUrlLabel QLabel
kurllabel.h
SettingsWidget QWidget
configwindow.h
1
QsciScintilla QFrame
Qsci/qsciscintilla.h
1
autokey-0.90.4/uic/hotkeysettings.ui0000664000175000017500000000613611723375735016517 0ustar chrischris Form 0 0 400 300 Form Modifiers: 10 Control true false Alt true false Shift true false Super true false Hyper 5 Key: %s Press to set KSeparator QFrame
kseparator.h
KPushButton QPushButton
kpushbutton.h
autokey-0.90.4/uic/settingswidget.ui0000664000175000017500000000607311723375735016477 0ustar chrischris SettingsWidget 0 0 316 91 Form Abbreviations: $abbr Set& Clear& Hotkey: $hotkey Set& Clear& Window Filter: $filter Set& Clear& Qt::Horizontal 40 20 KPushButton QPushButton
kpushbutton.h
autokey-0.90.4/uic/specialhotkeysettings.ui0000664000175000017500000000675311723375735020065 0ustar chrischris Form 0 0 531 397 Form Toggle monitoring using a hotkey Hotkey: $hotkey Qt::Horizontal 269 20 Set Clear Show configuration window using a hotkey Hotkey: $hotkey Qt::Horizontal 269 20 Set Clear Qt::Vertical 20 224 KPushButton QPushButton
kpushbutton.h
autokey-0.90.4/uic/compileuic.sh0000775000175000017500000000044111725514530015543 0ustar chrischris#!/bin/sh if [ $# -lt 1 ]; then uiFiles=`ls *.ui` else uiFiles=$@ fi for uiFile in $uiFiles; do echo "Processing $uiFile" filename=`basename $uiFile .ui` echo "Writing as ../src/lib/qtui/$filename.py" pykdeuic4 -o ../src/lib/qtui/$filename.py $uiFile done exit 0autokey-0.90.4/uic/enginesettings.ui0000664000175000017500000000377311723375735016465 0ustar chrischris Form 0 0 400 300 Form User Module Folder Any Python modules placed in this folder will be available for import by scripts. true 0 0 None selected Browse Qt::Vertical 20 40 autokey-0.90.4/uic/abbrsettings.ui0000664000175000017500000001157311724005052016102 0ustar chrischris Form 0 0 571 350 Form 0 0 true true false Trigger on: true Qt::Horizontal 40 20 Remove typed abbreviation Omit trigger character Match phrase case to typed abbreviation Ignore case of typed abbreviation Trigger when typed as part of a word Trigger immediately (don't require a trigger character) Qt::Vertical 20 40 KComboBox QComboBox
kcombobox.h
KSeparator QFrame
kseparator.h
autokey-0.90.4/uic/detectdialog.ui0000664000175000017500000000372511726560370016056 0ustar chrischris Form 0 0 400 240 Form Qt::LeftToRight Window information of selected window TextLabel TextLabel Window property selection Window class (entire application) Window title KButtonGroup QGroupBox
kbuttongroup.h
1
autokey-0.90.4/uic/folderpage.ui0000664000175000017500000000410111723375735015531 0ustar chrischris FolderPage 0 0 568 530 Form Qt::AlignCenter Open the folder in the default file manager Folder Settings Show in notification icon menu Qt::Vertical 20 40 KSeparator QFrame
kseparator.h
KUrlLabel QLabel
kurllabel.h
SettingsWidget QWidget
configwindow.h
1
autokey-0.90.4/debian/control0000664000175000017500000000474611744412515015135 0ustar chrischrisSource: autokey Priority: optional Section: utils Maintainer: Chris Dekter Build-Depends: python (>= 2.6), debhelper (>= 7), cdbs (>= 0.4.49) Build-Depends-indep: python-central (>= 0.6.0) Standards-Version: 3.9.3 XS-Python-Version: >= 2.6 Homepage: http://code.google.com/p/autokey/ Package: autokey-common Architecture: all Depends: ${python:Depends}, ${misc:Depends}, python-xlib, python-simplejson, python-pyinotify, wmctrl Replaces: autokey (<<0.61.4-0~0), autokey-gtk (<<0.70.4-0~0) Breaks: autokey (<<0.61.4-0~0), autokey-gtk (<<0.70.4-0~0) XB-Python-Version: ${python:Versions} Description: desktop automation utility - common data AutoKey is a desktop automation utility for Linux and X11. It allows the automation of virtually any task by responding to typed abbreviations and hotkeys. It offers a full-featured GUI that makes it highly accessible for novices, as well as a scripting interface offering the full flexibility and power of the Python language. . This package contains the common data shared between the various frontends. Package: autokey-qt Section: kde Architecture: all Depends: ${python:Depends}, ${misc:Depends}, python-kde4, python-qt4, python-qscintilla2, python-notify, autokey-common Replaces: autokey (<<0.61.4-0~0) Breaks: autokey (<<0.61.4-0~0) XB-Python-Version: ${python:Versions} Description: desktop automation utility - KDE version AutoKey is a desktop automation utility for Linux and X11. It allows the automation of virtually any task by responding to typed abbreviations and hotkeys. It offers a full-featured GUI that makes it highly accessible for novices, as well as a scripting interface offering the full flexibility and power of the Python language. . This package contains the Qt frontend. Package: autokey-gtk Section: gnome Architecture: all Depends: ${python:Depends}, ${misc:Depends}, python-gi, gir1.2-gtk-3.0, gir1.2-gtksource-3.0, gir1.2-glib-2.0, gir1.2-notify-0.7, python-dbus, zenity, autokey-common Replaces: autokey (<<0.61.4-0~0) Breaks: autokey (<<0.61.4-0~0) XB-Python-Version: ${python:Versions} Description: desktop automation utility - GTK+ version AutoKey is a desktop automation utility for Linux and X11. It allows the automation of virtually any task by responding to typed abbreviations and hotkeys. It offers a full-featured GUI that makes it highly accessible for novices, as well as a scripting interface offering the full flexibility and power of the Python language. . This package contains the GTK+ frontend. autokey-0.90.4/debian/compat0000664000175000017500000000000211354567042014720 0ustar chrischris7 autokey-0.90.4/debian/autokey-qt.postinst0000664000175000017500000000032511744413637017435 0ustar chrischris#!/bin/sh set -e update-alternatives --install /usr/bin/autokey autokey \ /usr/bin/autokey-qt 60 \ --slave /usr/share/man/man1/autokey.1.gz autokey.1.gz \ /usr/share/man/man1/autokey-qt.1.gz #DEBHELPER# autokey-0.90.4/debian/autokey-gtk.postinst0000664000175000017500000000032711744413637017600 0ustar chrischris#!/bin/sh set -e update-alternatives --install /usr/bin/autokey autokey \ /usr/bin/autokey-gtk 50 \ --slave /usr/share/man/man1/autokey.1.gz autokey.1.gz \ /usr/share/man/man1/autokey-gtk.1.gz #DEBHELPER# autokey-0.90.4/debian/autokey-qt.prerm0000664000175000017500000000023211744413637016674 0ustar chrischris#!/bin/sh set -e case "$1" in remove|deconfigure) update-alternatives --remove autokey /usr/bin/autokey-qt ;; esac #DEBHELPER# exit 0 autokey-0.90.4/debian/docs0000664000175000017500000000004411225340250014356 0ustar chrischrisACKNOWLEDGMENTS README PKG-INFO TODOautokey-0.90.4/debian/autokey-gtk.prerm0000664000175000017500000000023311744413637017036 0ustar chrischris#!/bin/sh set -e case "$1" in remove|deconfigure) update-alternatives --remove autokey /usr/bin/autokey-gtk ;; esac #DEBHELPER# exit 0 autokey-0.90.4/debian/autokey-common.postinst0000664000175000017500000000137211744242342020275 0ustar chrischris#!/bin/sh # postinst script for test # # see: dh_installdeb(1) set -e # summary of how this script can be called: # * `configure' # * `abort-upgrade' # * `abort-remove' `in-favour' # # * `abort-remove' # * `abort-deconfigure' `in-favour' # `removing' # # for details, see http://www.debian.org/doc/debian-policy/ or # the debian-policy package # dh_installdeb will replace this with shell code automatically # generated by other debhelper scripts. #DEBHELPER# exit 0 autokey-0.90.4/debian/rules0000775000175000017500000000064211744401132014572 0ustar chrischris#!/usr/bin/make -f DEB_PYTHON_SYSTEM=pycentral include /usr/share/cdbs/1/rules/debhelper.mk include /usr/share/cdbs/1/class/python-distutils.mk override_dh_fixperms: dh_fixperms chmod a-x debian/autokey-common/usr/share/icons/*/scalable/apps/*.svg # Add here any variable or target overrides you need. DEB_INSTALL_MANPAGES_autokey_qt = debian/autokey-qt.1 DEB_INSTALL_MANPAGES_autokey_gtk = debian/autokey-gtk.1 autokey-0.90.4/debian/autokey-common.prerm0000664000175000017500000000127111744242342017535 0ustar chrischris#!/bin/sh # prerm script for test # # see: dh_installdeb(1) set -e # summary of how this script can be called: # * `remove' # * `upgrade' # * `failed-upgrade' # * `remove' `in-favour' # * `deconfigure' `in-favour' # `removing' # # for details, see http://www.debian.org/doc/debian-policy/ or # the debian-policy package # dh_installdeb will replace this with shell code automatically # generated by other debhelper scripts. #DEBHELPER# exit 0 autokey-0.90.4/debian/pycompat0000664000175000017500000000000211202265033015254 0ustar chrischris2 autokey-0.90.4/debian/copyright0000664000175000017500000000365111744401132015450 0ustar chrischrisFormat: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: autokey Source: http://autokey.googlecode.com/ Files: * Copyright: © 2008-2012 Chris Dekter License: GPL-3+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. . This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this program. If not, see . . On Debian systems, the complete text of the GNU General Public License can be found in `/usr/share/common-licenses/GPL-3'. Files: debian/* Copyright: © 2009-2012, Chris Dekter © 2009-2012, Luke Faraone License: GPL-2+ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA . On Debian systems, the full text of the GNU General Public License version 2 can be found in the file `/usr/share/common-licenses/GPL-2'. autokey-0.90.4/debian/autokey-qt.install0000664000175000017500000000041511736013500017202 0ustar chrischrisusr/lib/python*/*-packages/autokey/qtui/ usr/lib/python*/*-packages/autokey/qtapp.py usr/share/icons/hicolor/scalable/apps/autokey.png usr/bin/autokey-qt usr/share/applications/autokey-qt.desktop usr/share/man/man1/autokey-qt.1 usr/share/kde4/apps/autokey/autokeyui.rc autokey-0.90.4/debian/autokey-gtk.install0000664000175000017500000000035111736013500017342 0ustar chrischrisusr/lib/python*/*-packages/autokey/gtkui/ usr/lib/python*/*-packages/autokey/gtkapp.py usr/share/icons/hicolor/scalable/apps/autokey.svg usr/bin/autokey-gtk usr/share/applications/autokey-gtk.desktop usr/share/man/man1/autokey-gtk.1 autokey-0.90.4/debian/autokey-common.install0000664000175000017500000000137611750077472020073 0ustar chrischrisusr/lib/python*/*-packages/autokey/common.py usr/lib/python*/*-packages/autokey/configmanager.py usr/lib/python*/*-packages/autokey/__init__.py usr/lib/python*/*-packages/autokey/interface.py usr/lib/python*/*-packages/autokey/iomediator.py usr/lib/python*/*-packages/autokey/model.py usr/lib/python*/*-packages/autokey/scripting.py usr/lib/python*/*-packages/autokey/service.py usr/lib/python*/*-packages/autokey/monitor.py usr/lib/python*/*-packages/autokey/macro.py usr/lib/python*/*-packages/autokey-*.egg-info usr/share/icons/hicolor/scalable/apps/autokey-status*.svg usr/share/icons/Humanity/scalable/apps/*.svg usr/share/icons/ubuntu-mono-dark/apps/48/*.svg usr/share/icons/ubuntu-mono-light/apps/48/*.svg usr/bin/autokey-run usr/share/man/man1/autokey-run.1 autokey-0.90.4/debian/changelog0000664000175000017500000007523511754441162015406 0ustar chrischrisautokey (0.90.4-0) unreleased; urgency=low * [GTK] Fix various issues with notifications * [GTK] Fix various issues with clipboard scripting * Apply patch from issue 199 - add support for Meta modifier -- Chris Dekter Tue, 15 May 2012 21:38:12 +1000 autokey (0.90.3-0) unreleased; urgency=low * Remove set_title from IndicatorNotifier, as it isn't supported in all versions of the AppIndicator API. -- Chris Dekter Fri, 4 May 2012 18:41:03 +1000 autokey (0.90.2-1) unreleased; urgency=low * Packaging change only: fix installation paths for ubuntu-mono-* icons -- Chris Dekter Wed, 2 May 2012 19:23:02 +1000 autokey (0.90.2-0) unreleased; urgency=low * [GTK] Run action should not be enabled for phrases * [GTK] Bring back support for Unity application indicator * [GTK] Fix a problem deleting items with older GTK3 versions * [GTK] Make showing the main window on startup the default behaviour when using the desktop file. * [GTK] Fix window filter not being applied after clicking OK in the filter settings dialog * [GTK] Only show one warning per session about externally modified config * Correctly handle scenario where a phrase and script with the same name exist * Add Unity to the list of environments messing with the Super key * [GTK] Enable toggling of tree row collapsed/expanded status -- Chris Dekter Tue, 1 May 2012 20:27:31 +1000 autokey (0.90.1-0) unreleased; urgency=low * [KDE] Fix two critical issues causing the application to not start -- Chris Dekter Sun, 22 Apr 2012 11:35:11 +1000 autokey (0.90.0-0) unreleased; urgency=low * [GTK] Port the GTK UI to GTK3 * [GTK] Add a help menu entry for the scripting API * [GTK] Get rid of the Abbreviation Selector as it's unmaintained and unused * [GTK] Add function to run the currently selected script * [GTK] Add a Tools menu, merge with View menu and add some new options * [GTK] Add the ability to enter custom trigger characters for abbreviations * [GTK] Obey desktop setting for monospace font in script editor * [GTK] Add a dialog for automatically configuring window filters * [GTK] Fix for issue 190 - special hotkeys won't save * [GTK] Save and restore the clipboard when using it to send text * [GTK] Allow manual sorting of the treeview on the name column * [GTK] Enable typeahead search in the treeview * [GTK] Add the ability to record keystrokes to phrases * [KDE] Add a help menu entry for scripting API, and enable standard "About KDE" entry * Add ability to store global variables in the script store * Completely remove the EvDev interface and daemon, and all related UIs * Use PNG icons for KDE and SVG icons for GNOME * Add cinnamon to the list of environments using Super key workaround -- Chris Dekter Fri, 20 Apr 2012 20:39:11 +1000 autokey (0.82.2-0) unreleased; urgency=low * [GTK] Fix critical bug for issue 185 - unable to save abbreviations * [KDE] Add a context menu for the log view * [KDE] Improve running of scripts from main window - handle exceptions and run in a worker thread -- Chris Dekter Wed, 7 Mar 2012 8:32:33 +1100 autokey (0.82.1-0) unreleased; urgency=low * Fix critical bug causing GTK version to no longer work * [KDE] Add a log view that can be shown/hidden using F4 * [KDE] Use the KDE global setting for monospace font instead of custom fonts -- Chris Dekter Mon, 5 Mar 2012 10:10:04 +1100 autokey (0.82.0-0) unreleased; urgency=low * Revive the KDE UI and bring it up to date with features developed since its deprecation * [KDE] Add a function for running a script from the GUI (2 second delay) * Implement enhancement issue 176 - add tab as a trigger character option * [KDE] Allow entry of custom trigger character ranges for abbreviations * Resolve issue 175 - wait for sub-scripts to complete before continuing * Resolve issue 177 - create a shallow copy of the namespace object for each script invocation * [KDE] Enable calltips and suggestions and update the API file * [KDE] Save and restore clipboard contents when using Send via Clipboard * Add RPM spec and update to build the KDE package as well * Add support for using the Hyper button in hotkeys * Fix incorrect logic in Mutter workaround causing it to always trigger -- Chris Dekter Sat, 3 Mar 2012 22:04:02 +1100 autokey (0.81.4-0) unreleased; urgency=low * Critical bugfix for mutter workaround method breaking on Python < 2.7 -- Chris Dekter Thu, 15 Dec 2011 10:40:02 +1100 autokey (0.81.3-0) unreleased; urgency=low * Handle exceptions during grab/ungrab of hotkeys * Prevent the X event loop from terminating if an exception occurs * Fix numpad hotkeys not working when numlock is on * Allow modifier keys to be auto-grabbed when setting a hotkey * Change icons for phrases and scripts to the standard ones for text files and python scripts * Refine the Gnome Shell workaround now that it's confirmed as a 'feature' * Improve clean shutdown again * Implement named, specified arguments for macros (work-in-progress) -- Chris Dekter Wed, 14 Dec 2011 17:22:12 +1100 autokey (0.81.2-0) unreleased; urgency=low * Improve compatibility with Gnome Shell by grabbing all hotkeys recursively * Set a prettier indicator icon name * Don't display the Interface tab in the preferences dialog if there is no need to change the setting * Fix engine.create_hotkey() and engine.create_abbreviation() -- Chris Dekter Sat, 3 Dec 2011 14:55:13 +1100 autokey (0.81.1-0) unreleased; urgency=low * Critical bug fix for error "unknown internal child: selection" on older versions of GTK. -- Chris Dekter Fri, 25 Nov 2011 09:08:23 +1100 autokey (0.81.0-0) unreleased; urgency=low * Implement issue 154 - external method for triggering scripts/phrases/folders * Add auto-mnemonic to the first 9 items in the popup menu * Allow window filters on folders to be applied recursively to children * Another attempt at improving grab/ungrab hotkey behaviour and performance * Fix bug where our own changes were sometimes detected by file monitor * Implement keyboard.wait_for_keypress() for scripts * Implement mouse.wait_for_click() for scripts * Allow multiple abbreviations to be assigned * Allow hotkeys and abbreviations to be re-used when the window filter is different * Rework validation framework, and do detailed validation at save time * Bring back a version of the macro system for phrases * Add a cursor-positioning macro * Add a macro that allows scripts to be run and the results used * Bring back the option to configure notification icon style * Add a dark-style notification icon * Bring back OSD notifications for script errors * Change notification icon to red on script errors * Tweak alignment and spacing of all windows and dialogs to conform to GNOME HIG * Add a "Report a Bug" help menu option * Disable the "Trigger On" combo box when trigger immediately is enabled * Drop the separate Save/Revert buttons and move Save into the toolbar * Make auto-saving the default mode * Allow creation without needing to select a parent folder first * Fix intermittent hanging during shutdown, and wait for all threads to terminate before quitting * Focus correct control on Set Abbreviations dialog * Fix bug where folders are always created at the top level * Enable window class matching features of wmctrl -- Chris Dekter Wed, 22 Nov 2011 16:20:55 +1100 autokey (0.80.3-0) unreleased; urgency=low * Fix a bug where Firefox/Thunderbird were not responding to keyboard events * Fix for issue 150 - window titles containing unicode characters were not handled correctly * Re-choose interface type each time a version upgrade occurs * Implement a better way of determining Xorg version * Make XRecord the default interface type if we can't determine Xorg version * Disable daemon and don't start it during postinst if Xorg supports XRecord -- Chris Dekter Wed, 12 Oct 2011 11:54:02 +1100 autokey (0.80.2-0) unreleased; urgency=low * Rewrite X interface to use a single-threaded queue model * Fix a bug where keyboard was not ungrabbed after creating a hotkey * Don't create file names with spaces at the start or end * Get detection of window creation working again and use CreateNotify instead of MapNotify * Grab/ungrab hotkeys using window title and class for the filter * Minor tweaks and improvements to EvDev and ATSPI interfaces * Fix a bug where keyboard ungrabbing stops working after keymap change * Correctly ungrab and regrab hotkeys when the keymap is changed * Make both parts of the window class available to match on -- Chris Dekter Sat, 8 Oct 2011 14:56:01 +1100 autokey (0.80.1-0) unreleased; urgency=low * Fix for issue 143 - deadlocks in python-xlib during high-speed events * Fix for issue 144 - handle phrase/script names that have no safe chars -- Chris Dekter Fri, 30 Sep 2011 09:33:21 +1100 autokey (0.80.0-0) unreleased; urgency=low * Rewrite persistence layer - use a real filesystem layout of folders and files to persist configuration and allow direct editing in other programs * Create conversion path from previous single configuration file * Add inotify-based file monitoring and reload configuration in real-time if modified externally * Implement reloading of the keyboard mapping when it is changed * Simplify and improve implementation of dynamic keyboard remapping * Better detection and use of ISO Level 3 shift (Alt-Grid) * Make AutoKey compatible with QT4 apps having the event rate bug * Add a configurable window title regex to allow specifying which apps should have the workaround performed * Implement a Dbus session service and use it to trigger display of the main window when a second instance of the program is started * Add ability for scripts to get the active window title and class * Add sample script that shows the active window information * Use application indicator instead of GTK notification icon if available * Use icon names rather than file names to allow icon themes to change our icons * Redesigned application icon * Add a standard notification area icon that is less intrusive * Make enabling/disabling the notification icon instant instead of requiring a restart * Give icons more sensible names * Deprecate and remove the KDE GUI * Stop building transitional 'autokey' package * Remove nogui code and documentation - it's been broken for a long time * Fix drag and drop in the tree view * Silence various GTK builder warnings * Add F2 as an accelerator for Rename * Completely rework item naming and renaming by adding a separate dialog to perform this function * Fix for issue 137 - add locks around display for grab/ungrab during phrase send * Implement issue 110 - add option to clone a phrase/script * Implement issue 133 - add scripting function to click mouse relative to current location * Make delete nicer by not prompting for every single selected item * Fix problem where having two identically-named top-level folders was impossible * Fix divergence between list of folders in configmanager and treemodel * Simulate press and release of modifier keys when sending modified symbols * Merge patch for issue 140 - don't use hardcoded errno value -- Chris Dekter Thu, 22 Sep 2011 14:26:08 +1000 autokey (0.71.3-1) unreleased; urgency=low * Documentation change only - license in README -- Chris Dekter Sat, 16 Jul 2011 10:45:13 +1000 autokey (0.71.3-0) unreleased; urgency=low * Fix for issue 108 - cannot upgrade a v0.5x config file any more * Fix for issue 107 - clipboard get functions are not using unicode * Implement enhancement 109 - add general keyword arguments to script dialogs * If unable to connect to the daemon on startup, retry a few times * If interface initialisation fails, still initialise necessary objects to allow user to modify interface configuration. -- Chris Dekter Fri, 1 Apr 2011 11:24:11 +1100 autokey (0.71.2-0) unreleased; urgency=low * Update all licenses to GPLv3 and add where missing * Update package license to GPLv3 * Add missing COPYING file * Fix engine.run_script() (issue 97) * Fix for issue 95, dropping next character after hotkey -- Chris Dekter Fri, 28 Jan 2011 18:24:11 +1100 autokey (0.71.1-1) unreleased; urgency=low * Packaging change only, add missing xml data file for GTK UI -- Chris Dekter Sun, 24 Oct 2010 09:59:23 +1100 autokey (0.71.1-0) unreleased; urgency=low * [gtk] Handle situation where no folders exist (issue 62) * Remove KeyboardInterrupt handling as it didn't work anyway (issue 3) * Fix window.get_active_geometry() (issue 77) * Don't use absolute path name to application icon * Add 'status' function to init script to meet Redhat sysv init specs -- Chris Dekter Fri, 22 Oct 2010 13:21:11 +1100 autokey (0.71.0-0) unreleased; urgency=low * Add an import/export facility (issue 58) * Add support for hotkeys with no modifiers (issue 53) * Fix shift unable to be used as a standalone modifier for a hotkey (issue 59) * Add ability to separately press and release keys using scripting API (issue 52) * Add some debug info around keyboard remap operation * Disable configuration of popup menu focus behaviour for now as it's causing too many hard-to-diagnose bugs * Don't grab keyboard when recording a macro * Don't save changes to a global hotkey if the hotkey is disabled * Add assertions to detect invalid hotkeys being created -- Chris Dekter Fri, 30 Jul 2010 12:33:10 +1000 autokey (0.70.5-1) unreleased; urgency=low * Packaging fix only - prevent errors with akicon-status.png by specifiying replaces and breaks in autokey-common -- Chris Dekter Mon, 19 Jul 2010 10:57:23 +1000 autokey (0.70.5-0) unreleased; urgency=low * Improve window name determination by using the FreeDesktop specified atom names * Fix handling of Tab key in service and iomediator * Disable detection of keymap changes, as this seems to cause weird interaction with dynamic key remapping * Fix misnamed argument in fake_keypress() * [gtk] Implement option to configure tray icon style (issue 51) * Fix naming convention of main window (too many uses of "Configure") * [kde] Make notification tooltip translatable -- Chris Dekter Wed, 14 Jul 2010 15:11:15 +1000 autokey (0.70.4-0) unreleased; urgency=low * Make sending text via keyboard dramatically faster by eliminating 2 RTT per character * Remove event replaying code as it is no longer needed and too unreliable * Make phrase execution sequential using a lock, to prevent phrases stomping on each other's output when multiple phrases are triggered one after the other * Only reapply modifiers when entire string has been typed * Remove locks from iomediator as they were made redundant by keyboard grabbing * Fix handling of space by adding it to the XK/AK map * Tweak get_window_title() courtesy of patch by Joseph Reagle * Change text on interface settings page to reflect Record being fixed in x.org v1.7.6 * Fix _chooseInterface() and update it to reflect Record being fixed in x.org v1.7.6 * Disable substring abbreviation crosscheck - too many spurious warnings * Set initial state of Caps and Numlock using the keyboard LED mask * Get rid of lock state setting in Record interface, wasn't working anyway * Initialise global hotkeys after creating fresh config on first run * Fix for issue 42: Set showPopupHotkey to be a no-op closure in KDE version -- Chris Dekter Sun, 25 Apr 2010 15:31:15 +1000 autokey (0.70.3-1) unreleased; urgency=low * Fix tiny oops in interface.py -- Chris Dekter Wed, 21 Apr 2010 21:28:02 +1000 autokey (0.70.3-0) unreleased; urgency=low * Automatically adjust keyboard mapping to allow sending of non-mapped characters in phrases * Remove untypable character validation as it's no longer needed * Catch BadWindow errors during initial hotkey grabs * Prevent abbreviations triggering in the abbreviation settings dialog * [kde] Enable auto-sizing of treeview columns * [gtk] Wrap clipboard calls in gtk.gdk.threads_enter() and leave() * [gtk] Raise a nicer exception if no text was returned from selection or clipboard * [gtk] Fix incorrect validation message in phrase page * Enable config file upgrade from 0.6x.x to to 0.70.0 * Add some logging around config upgrades -- Chris Dekter Wed, 21 Apr 2010 19:28:02 +1000 autokey (0.70.2-0) unreleased; urgency=low * Fix for issue 35 - Cleared hotkeys show in column * Remove send_unicode_char() as it no longer works * Handle missing modifier masks by warning (and not crashing) * Don't grab hotkey combinations for Caps and Num if they aren't mapped * [gtk] Autosize treeview columns * [kde] Remember column and splitter positions * [gtk] Fix hang when filling the clipboard/mouse selection * Exorcise all remaining traces of SourceForge site * Change left click action of notification icon to show the configuration window * When using XRecord interface, get initial state of lock keys * Distinguish between numpad and ordinary keys * Fix numpad key decoding when both numlock and shift are active * Grab the keyboard while grabbing the key for a hotkey in hotkey settings dialog * Implement configuration option to send phrase via different modes * Implement validation to check for untypable characters in a phrase * Improve documentation for scripting interface (issue 37) * Don't log list of phrases/scripts in debug mode as it can be a security risk * Disable abbreviation popup hotkey by default * Make hotkey and abbreviation validation messages more informative * Detect substring abbreviation conflicts when validating -- Chris Dekter Sat, 17 Apr 2010 10:23:02 +1000 autokey (0.70.1-0) unreleased; urgency=low * Add extra columns to treewidget to display abbreviation and/or hotkey * Always prompt before deleting anything * Much nicer determination of new selection after deleting an item * [kde] Improve treewidget behaviour when creating phrases/scripts * [kde] Get rid of unused help menu entries * Fix a number of bugs around prompting to save and autosaving * [gtk] Fix for issue 29 - Clicking "Yes" for saving a phrase doesn't save the phrase * Use shutil.copy2 to preserve config file's timestamp * Extensive rewrite of keyboard mapping code * Prevent spurious hotkey grabs on window create * Flush generated keyboard events before ungrabbing the keyboard * Add a fake_keypress() to scripting keyboard class to send events using xtest * Fix bug where word characters were not detected correctly in non-English locales * Add dependency and import handling for differing JSON libraries in Python 2.5 -- Chris Dekter Tue, 13 Apr 2010 20:26:12 +1000 autokey (0.70.0-0) unreleased; urgency=low * Persist configuration using json instead of pickle * Make hotkeys exclusive - prevent other applications from receiving them * Hotkeys are grabbed globally if they have no filter * Hotkeys are grabbed only in matching windows if they have a filter * Grab keyboard while sending strings to prevent user-typed input mixing with output * Fix problem where hotkeys with backspace, tab and enter would not work * Improve window name determination * Build a map of modifier masks * Use modifier masks to correctly emit modified keys instead of using Xtest -- Chris Dekter Fri, 9 Apr 2010 20:26:12 +1000 autokey (0.61.7-0) unreleased; urgency=low * Fix incorrect reference to instance variable in KDE version * Fix incorrect method signature in KeyGrabber class * ConfigManager uses version from common.py now -- Chris Dekter Tue, 30 Mar 2010 21:30:52 +1000 autokey (0.61.6-0) unreleased; urgency=low * Fix problem with autostart in GTK version - issue #27 * Allow system.exec_command to be used with long-running processes * Fix - hotkey dialog does not re-enable the "Press to set" button after cancelling - issue #23 * Slight improvement to installation instructions * Fix - special hotkeys - cannot assign previously cleared hotkey - issue #9 * Revert status icon for GTK version - it only looked good on one version of one distro * Patch holes in gettext support for GTK version (patch contribued by mail@paddy-net.com) -- Chris Dekter Thu, 25 Mar 2010 21:30:52 +1000 autokey (0.61.5-0) unreleased; urgency=high * SECURITY UPDATE: arbitrary file overwriting via symlinks (LP: #538471) - Store files for the EvDev daemon in FHS-specified locations - debian/autokey-common.init: Set pidfile path to "/var/run/autokey-daemon.pid" - src/lib/common.py: Set DOMAIN_SOCKET_PATH to "/var/run/autokey-daemon" - CVE-2010-0398 -- Chris Dekter Sat, 20 Mar 2010 21:30:52 +1000 autokey (0.61.4-0) unreleased; urgency=low [Chris Dekter] * Combine GTK and QT versions into single source tree [Luke Faraone] * Update package build to build autokey-gtk, autokey-qt and autokey-common packages -- Chris Dekter Sun, 28 Feb 2010 08:49:52 +1000 autokey (0.61.3-0) unreleased; urgency=low [ Luke Faraone ] * Handle invalid or empty pidfiles in src/daemon.py. * Lower build-depends requirements to minimum Python versions. [ Chris Dekter ] * Add --error-handler to debian/rules -- Chris Dekter Sat, 27 Feb 2010 08:49:52 +1000 autokey (0.61.2-0) unreleased; urgency=low * Bring back cut/copy/paste item menu options * Add 'engine' class to scripting framework to enable access to AutoKey internals * Add a configurable user module folder for import into scripts * Enable multiple selection mode in treeview and update all necessary interactions to work correctly * Enable inline renaming of items in treeview, get rid of title and description fields from the various pages -- Chris Dekter Wed, 6 Jan 2010 10:38:05 +1000 autokey (0.61.0b-0) unreleased; urgency=low [ Chris Dekter ] * Fix bug with sending newlines from scripts * Fix another crash in the EvDev daemon related to button conversions [ Luke Faraone ] * Fix l10n bug in daemon.py which caused breakage on package removal -- Chris Dekter Fri, 11 Dec 2009 10:38:05 +1000 autokey (0.61.0a-0) unreleased; urgency=low * Fix/improve mouse button conversion in EvDev daemon -- Chris Dekter Sun, 29 Nov 2009 10:38:05 +1000 autokey (0.61.0-0) unreleased; urgency=low * Add mouse click recording and playback capabilities * Rework record functionality to work as a full macro facility * Add ability to view tracebacks from script errors in GUI * Change sample scripts so they work on both GTK and KDE versions * Handle scenario where xlib record module is not available * Don't undo last expansion if the mouse has been clicked * Improvements to AT-SPI interface - now gives the real window title -- Chris Dekter Mon, 26 Oct 2009 10:38:05 +1000 autokey (0.60.7a-0) unreleased; urgency=low * Fix syntax error in configwindow.py * Fix a few issues with scripting window management library -- Chris Dekter Tue, 20 Oct 2009 10:38:05 +1000 autokey (0.60.7-0) unreleased; urgency=low * Add window management capabilities to scripting API * Minor GUI tweaks to bring into line with GTK version * Fix bug where keyboard was sometimes lost after resume from S3 * Flush event buffer after executing keyboard events from a script -- Chris Dekter Mon, 19 Oct 2009 12:38:05 +1000 autokey (0.60.6-0) unreleased; urgency=low * Fix bug where html phrases where not loaded as plain text -- Chris Dekter Wed, 30 Sep 2009 12:38:05 +1000 autokey (0.60.5-0) unreleased; urgency=low * Change to using pickle instead of cPickle due to a bug in the latter * Bring in changes to common modules needed for GTK version * Fix design flaw in pickle error trapping code * Fix not being sent at the end of unicode char entry * Fix service crashing when undoing an expansion with backspace * Use Phrase instead of Script in some nogui functions -- Chris Dekter Mon, 31 Aug 2009 12:38:05 +1000 autokey (0.60.4-0) unreleased; urgency=low * Revert to sending phrases via keyboard rather than X selection -- Chris Dekter Sun, 23 Aug 2009 12:38:05 +1000 autokey (0.60.3-0) unreleased; urgency=low * Initial version of nogui API * Fix some more unicode issues with the new GUI * Remove import of configobj from configmanager -- Chris Dekter Fri, 21 Aug 2009 12:38:05 +1000 autokey (0.60.2-0) unreleased; urgency=low * Fix unicode issues when saving and sending keys * Fix QT4 workaround current value not being loaded in the settings dialog * Remove relative imports as Python 2.5 can't handle them properly -- Chris Dekter Tue, 18 Aug 2009 17:38:05 +1000 autokey (0.60.1-0) unreleased; urgency=low [ Chris Dekter ] * Point Manual link in Help menu at Wiki index * Get rid of default phrases that no longer work * Add some default scripts * Remove reference to showPopupHotkey in ConfigManager [ Luke Faraone ] * Change over most of the URLs from sourceforge to the new project hosting at Google Code -- Chris Dekter Tue, 18 Aug 2009 17:38:05 +1000 autokey (0.60.0-0) unreleased; urgency=low * Implement scripting interface (ScriptRunner) * Rebuild UI using PyKDE/PyQT * Refactor ExpansionService, split into PhraseRunner and ScriptRunner * Implement libraries to provide script namespaces * Add option to undo abbreviation expansion using backspace -- Chris Dekter Thu, 9 Jul 2009 20:38:05 +1000 autokey (0.54.5-0) unreleased; urgency=low * debian/control: Changed Arch:Any to all since we don't build platform-specific binaries * debian/control: Bump standards version to 3.8.2 * Remove redundant encoding in .desktop file which is deprecated in latest Freedesktop.org spec -- Luke Faraone Sun, 12 Jul 2009 17:55:46 -0400 autokey (0.54.4-0) unreleased; urgency=low [ Chris Dekter ] * Add support for numpad keys, and a few other standard keys that were not handled * Add AT-SPI interface * Add configuration screen to choose interface type (requires restart) * Make tooltip display 'running' or 'paused' depending on service status * Fix hotkeys not working if Capslock or Numlock are on * Get rid of interface switching code * Create manpage * Fix bug where the system would sometimes lock up a while after displaying a phrase menu * Fix bug with EvDev interface where repeated keypress (key being held down) are ignored * Fix PhraseMenu sorting, and make it sort alphabetically if not by usage count * Disable mnemonics in PhraseMenu - or phrases with _ in title not displayed correctly * Minor tweak to fix for 2782632, as it broke macros in other ways. * Improve logging - log to file if run without -v * Add -c | --configure option to forcibly show the configure dialog on startup * Fix annoying bug where right-click of treeview doesn't always pick up the right node * Implement proper handling of Unicode/UTF-8 - all internal strings are now unicode instances * Implement yet another (rather hacky) fix for Alt-Gr issues. [ Luke Faraone ] * debian/control: Fixed improper line lengths * debian/copyright: Fix usage of (C) with © (the latter is not valid) * debian/copyright: Credit original author * debian/copyright: Remove dh_make template text * debian/autokey.init: Add required option force-reload * src/lib/autokey.py: Create configuration directory if it doesn't already exist -- Chris Dekter Wed, 8 Jul 2009 20:38:05 +1000 autokey (0.54.3-0) unreleased; urgency=low * Fix daemon sometimes dying when receiving events with integer code * Make daemon find devices using by-path entries instead of relying on HAL * Update TODO * Make Comment in .desktop entry nicer. Add GenericName -- Chris Dekter Tue, 7 Jul 2009 08:38:05 +1000 autokey (0.54.2-0) unreleased; urgency=low * Fix daemon dropping connection on mouse clicks * Add logging and command line option to set level to debug * Add LSB info to daemon script -- Chris Dekter Sun, 5 Jul 2009 18:38:05 +1000 autokey (0.54.1-0) unreleased; urgency=low * Refactor daemon so it starts successfully during boot * Fix problem where daemon exits if run on a PC without a touchpad * Fix initialisation of global hotkeys * Fix inability to configure any hotkeys -- Chris Dekter Sun, 5 Jul 2009 00:11:05 +1000 autokey (0.54.0-0) unreleased; urgency=low * Add new EvDev interface as an alternative where XRecord is not available. * Make EvDev the default (changing to XRecord will require hacking the code, for now). * Fix bug where Cut/Copy/Paste entries under Edit menu were not correctly disabled. * * Includes fix for: * 2782632 - Capitalization is applied to macros and specialkeys -- Chris Dekter Sat, 4 Jul 2009 12:11:05 +1000 autokey (0.53.1-2) unreleased; urgency=low * Alter cut/copy/paste to release standard key bindings to normal functions * Break sub plugin input at line endings, in addition to spaces * Add donate button as per ER 2789380 -- Chris Dekter Thu, 21 May 2009 19:13:37 +1000 autokey (0.53.0-2) unreleased; urgency=low * Alter build-depends to allow compatibility with hardy * Added a fully functioning distutils-based installer. * Added links to the Manual and FAQ in the Help menu. * Includes fixes for the following defects: * 2782607 - Issues with '<' sign * 2783421 - Multiple use of $(sub ) produces superfluous backspace * 2720487 - $(sub ) problem with parentheses, quotes etc. * 2783016 - Sub: '~' does not work as split character with DE keyboard * 2782526 - not working * 2710535 - Exception on repeated window switching * 2710118 - Multiple abbreviation windows opened * 2697670 - Exception when using predefined hotkeys. * 2697653 - Can't assign hotkey -- Chris Dekter Tue, 12 May 2009 19:35:37 +1000 autokey-0.90.4/autokey-qt0000664000175000017500000000145011723375735014334 0ustar chrischris#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys from autokey.qtapp import Application a = Application() a.main() sys.exit(0) autokey-0.90.4/autokey-gtk0000664000175000017500000000145111517470040014460 0ustar chrischris#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys from autokey.gtkapp import Application a = Application() a.main() sys.exit(0) autokey-0.90.4/TODO0000664000175000017500000000013111665615436012771 0ustar chrischrisThese will probably never happen...: Get rid of commented out code Add method comments autokey-0.90.4/INSTALL0000664000175000017500000000067311744402352013332 0ustar chrischris$Id: INSTALL 534 2012-04-21 00:52:58Z cdekter@gmail.com $ The full application can be installed using the setup script: python setup.py install Alternatively, you can build Debian packages using the following command: dpkg-buildpackage -us -uc cd ../ Then to install the GTK version: sudo dpkg -i autokey-gtk_.deb autokey-common_.deb Or the Qt version: sudo dpkg -i autokey-qt_.deb autokey-common_.deb autokey-0.90.4/COPYING0000664000175000017500000007733111517470127013344 0ustar chrischris GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS autokey-0.90.4/autokey.spec0000664000175000017500000001154311736013500014626 0ustar chrischris# # This file and all modifications and additions to the pristine # package are under the same license as the package itself. # #define python macros for openSUSE < 112 %{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} %{!?python_sitearch: %global python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")} Name: autokey Version: 0.82.0 Release: 1 License: GPLv3 Summary: Desktop automation utility Url: http://autokey.googlecode.com Group: System/X11/Utilities Source: %{name}_%{version}.tar.gz BuildRequires: python-base BuildRequires: update-desktop-files BuildRoot: %{_tmppath}/%{name}-%{version}-build %if 0%{?suse_version} > 1110 # This works on newer version # on older version it dies misserably if used BuildArch: noarch %endif %description AutoKey is a desktop automation utility for Linux and X11. It allows the automation of virtually any task by responding to typed abbreviations and hotkeys. It offers a full-featured GUI that makes it highly accessible for novices, as well as a scripting interface offering the full flexibility and power of the Python language. %package common License: GPLv3 Summary: Desktop automation utility -- Common Files Group: System/X11/Utilities Requires: python-simplejson Requires: python-pyinotify Requires: python-xlib Requires: wmctrl Recommends: %{name}-gtk %py_requires %description common AutoKey is a desktop automation utility for Linux and X11. It allows the automation of virtually any task by responding to typed abbreviations and hotkeys. It offers a full-featured GUI that makes it highly accessible for novices, as well as a scripting interface offering the full flexibility and power of the Python language. %package gtk License: GPLv3 Summary: Desktop automation utility -- GTK+ Interface Group: System/X11/Utilities Requires: %{name}-common = %{version} Requires: python-gtk Requires: python-gtksourceview Requires: python-notify Requires: zenity %py_requires %description gtk AutoKey is a desktop automation utility for Linux and X11. It allows the automation of virtually any task by responding to typed abbreviations and hotkeys. It offers a full-featured GUI that makes it highly accessible for novices, as well as a scripting interface offering the full flexibility and power of the Python language. %package qt License: GPLv3 Summary: Desktop automation utility -- KDE Interface Group: System/X11/Utilities Requires: %{name}-common = %{version} Requires: python-kde4 Requires: python-qt4 Requires: python-qscintilla2 Requires: python-notify %py_requires %description qt AutoKey is a desktop automation utility for Linux and X11. It allows the automation of virtually any task by responding to typed abbreviations and hotkeys. It offers a full-featured GUI that makes it highly accessible for novices, as well as a scripting interface offering the full flexibility and power of the Python language. %prep %setup -n %{name}_%{version} find src/lib -name "*.py" -exec sed -i '/^#!\/usr\/bin\/env python$/d' {} ";" %build %{__python} setup.py build %install %{__python} setup.py install --prefix=%{_prefix} --root=%{buildroot} %suse_update_desktop_file autokey-gtk DesktopSettings %files common %defattr(-,root,root) %doc ACKNOWLEDGMENTS README %dir %{python_sitelib}/autokey %{python_sitelib}/autokey/*.py* %exclude %{python_sitelib}/autokey/gtkapp.py* %{python_sitelib}/%{name}-%{version}-py%{py_ver}.egg-info %{_datadir}/icons/hicolor/scalable/apps/autokey-status*.svg %{_datadir}/icons/hicolor/scalable/apps/autokey.png %{_datadir}/icons/Humanity/scalable/apps/*.svg %{_datadir}/icons/ubuntu-mono-dark/scalable/apps/*.svg %{_datadir}/icons/ubuntu-mono-light/scalable/apps/*.svg %{_bindir}/autokey-run %{_mandir}/man1/autokey-run.1* %files gtk %defattr(-,root,root) %{_bindir}/autokey-gtk %{python_sitelib}/autokey/gtkapp.py* %{python_sitelib}/autokey/gtkui/ %{_datadir}/applications/autokey-gtk.desktop %{_datadir}/icons/hicolor/scalable/apps/autokey.svg %{_mandir}/man1/autokey-gtk.1* %files qt %defattr(-,root,root) %{_bindir}/autokey-qt %{python_sitelib}/autokey/qtapp.py* %{python_sitelib}/autokey/qtui/ %{_datadir}/applications/autokey-qt.desktop %{_datadir}/icons/hicolor/scalable/apps/autokey.png %{_mandir}/man1/autokey-qt.1* %post common /bin/touch --no-create %{_datadir}/icons/hicolor &>/dev/null || : %postun common if [ $1 -eq 0 ] ; then /bin/touch --no-create %{_datadir}/icons/hicolor &>/dev/null /usr/bin/gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || : fi %posttrans common /usr/bin/gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || : %changelog autokey-0.90.4/PKG-INFO0000664000175000017500000000107211754440521013371 0ustar chrischrisMetadata-Version: 1.0 Name: autokey Version: 0.90.4 Summary: Desktop automation utility Home-page: http://autokey.googlecode.com/ Author: Chris Dekter Author-email: cdekter@gmail.com License: GPL v3 Description: AutoKey is a desktop automation utility for Linux and X11. It allows the automation of virtually any task by responding to typed abbreviations and hotkeys. It offers a full-featured GUI that makes it highly accessible for novices, as well as a scripting interface offering the full flexibility and power of the Python language. Platform: UNKNOWN autokey-0.90.4/MANIFEST.in0000664000175000017500000000001711357620726014036 0ustar chrischrisinclude INSTALLautokey-0.90.4/autokey-run0000664000175000017500000000355011662107715014510 0ustar chrischris#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys, dbus, optparse if __name__ == "__main__": p = optparse.OptionParser() p.add_option("-s", "--script", help="Run a script", action="store", type="string") p.add_option("-p", "--phrase", help="Paste a phrase", action="store", type="string") p.add_option("-f", "--folder", help="Show a folder popup menu", action="store", type="string") p.set_usage("autokey-run -[s|p|f] [name]") options, args = p.parse_args() if (not options.script) and (not options.phrase) and (not options.folder): p.print_usage() sys.exit(1) bus = dbus.SessionBus() try: dbusService = bus.get_object("org.autokey.Service", "/AppService") if options.script: dbusService.run_script(options.script, dbus_interface = "org.autokey.Service") elif options.phrase: dbusService.run_phrase(options.phrase, dbus_interface = "org.autokey.Service") elif options.folder: dbusService.run_folder(options.folder, dbus_interface = "org.autokey.Service") sys.exit(0) except dbus.DBusException, e: print >> sys.stderr, str(e) sys.exit(1) autokey-0.90.4/ACKNOWLEDGMENTS0000664000175000017500000000420411633242033014436 0ustar chrischrisThanks go to Sam Peterson (peabody17), the original developer of AutoKey, for allowing me to join his project and not objecting when I more or less took it over. My sincerest thanks to tiheum, the designer of the Faenza icon set for Linux. One of his designs is the basis of AutoKey's icon and his superb icon set makes my desktop a joy to use. I'd like to pass my gratitude to all who have made donations to the AutoKey project: theoa, jschall, mfseeker, jflevi, keycombat, rkcallahan, bkudria, riffian, and many others. Many thanks to the developers of xdotool, from which I copied the idea of dynamically modifying the keyboard map to send non-mapped characters. I also want to thank the developers of Phrase Express. Their application's UI was the inspiration for AutoKey's configuration window. The below are the original acknowledgements from Sam Peterson. --------------------------------------------------------------------- Above all, I wish to thank my wife Margaret Tam. You're always there when I need you. You're always there to support me throughout the good and bad in life. I love you my dear. I'd like to thank Eddie Bell for his keylogger example. It was the turning point in my early development of the program that gave me what I needed for monitoring keyboard input. I'd especially like to thank Peter Liljenberg for writing the python-xlib extension, without which this software wouldn't have been possible. I'd like to acknowledge Textpander, Texter and the Autohotkey developers whose software was an inspiration to this software. I'd like to thank all of the people on the Ubuntu forums who have given me their feedback and/or tried the program (in no particular order or preference): Vadi, Scarath, jackocleebrown, ugm6hr, RebounD11, Bungo Pony, forrestcupp, kevdog, conehead77, PartisanEntity, DjBones, antisocialist, Specter043, Linuxratty, SanskritFritz, Martje_001, tribaal, Vitamin-Carrot. I apologize profusely if I have forgotten to mention anyone. I'd like to thank SourceForge for hosting the project. Lastly I'd like to thank Guido van Rossum for authoring the Python programming language, and RMS for gcc and GNU Emacs. autokey-0.90.4/extractDoc.py0000664000175000017500000000307011723605036014746 0ustar chrischris# -*- coding: utf-8 -*- # Copyright (C) 2008 Chris Dekter # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import sys, inspect sys.path.append("./src/lib") import scripting if __name__ == "__main__": outFile = open("src/lib/qtui/data/api.txt", "w") for name, attrib in inspect.getmembers(scripting): if inspect.isclass(attrib) and not (name.startswith("_") or name.startswith("Gtk")): for name, attrib in inspect.getmembers(attrib): if inspect.ismethod(attrib) and not name.startswith("_"): doc = attrib.__doc__ lines = doc.split('\n') try: apiLine = lines[3].strip() docLine = lines[1].strip() except: continue outFile.write(apiLine[9:-1] + " " + docLine + '\n') outFile.close()autokey-0.90.4/setup.py0000664000175000017500000000530611754440521014012 0ustar chrischris# -*- coding: utf-8 -*- # Copyright (C) 2011 Chris Dekter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from distutils.core import setup setup( name="autokey", version="0.90.4", author="Chris Dekter", author_email="cdekter@gmail.com", url="http://autokey.googlecode.com/", license="GPL v3", description="Desktop automation utility", long_description="""AutoKey is a desktop automation utility for Linux and X11. It allows the automation of virtually any task by responding to typed abbreviations and hotkeys. It offers a full-featured GUI that makes it highly accessible for novices, as well as a scripting interface offering the full flexibility and power of the Python language.""", #py_modules=["autokey", "configurationmanager", "expansionservice", "interface", # "iomediator", "phrase", "phrasemenu", "ui"], package_dir={"autokey": "src/lib"}, packages=["autokey", "autokey.gtkui", "autokey.qtui"], package_data={"autokey.qtui" : ["data/*"], "autokey.gtkui" : ["data/*"]}, data_files=[("/usr/share/icons/hicolor/scalable/apps", ["config/autokey.svg", "config/autokey.png", "config/autokey-status.svg", "config/autokey-status-dark.svg", "config/autokey-status-error.svg"]), ("/usr/share/icons/Humanity/scalable/apps", ["config/Humanity/autokey-status.svg", "config/Humanity/autokey-status-error.svg"]), ("/usr/share/icons/ubuntu-mono-dark/apps/48", ["config/ubuntu-mono-dark/autokey-status.svg", "config/ubuntu-mono-dark/autokey-status-error.svg"]), ("/usr/share/icons/ubuntu-mono-light/apps/48", ["config/ubuntu-mono-light/autokey-status.svg", "config/ubuntu-mono-light/autokey-status-error.svg"]), ("/usr/share/applications", ["config/autokey-qt.desktop", "config/autokey-gtk.desktop"]), ('share/man/man1/', ['doc/man/autokey-qt.1', 'doc/man/autokey-gtk.1', 'doc/man/autokey-run.1']), ('/usr/share/kde4/apps/autokey' , ['config/autokeyui.rc'])], scripts=['autokey-qt', 'autokey-gtk', 'autokey-run'] #packages=["plugin"] ) autokey-0.90.4/README0000664000175000017500000000075311610156722013157 0ustar chrischris$Id: README 312 2011-07-16 00:39:46Z cdekter@gmail.com $ Read INSTALL for installation instructions. AutoKey is a desktop automation utility for Linux and X11. It allows the automation of virtually any task by responding to typed abbreviations and hotkeys. It offers a full-featured GUI that makes it highly accessible for novices, as well as a scripting interface offering the full flexibility and power of the Python language. This software is licensed under the GPLv3 software license.autokey-0.90.4/src/lib/qtui/data/0000775000175000017500000000000011754440541015526 5ustar chrischrisautokey-0.90.4/src/lib/gtkui/data/0000775000175000017500000000000011754440541015667 5ustar chrischrisautokey-0.90.4/src/lib/qtui/0000775000175000017500000000000011754440541014615 5ustar chrischrisautokey-0.90.4/src/lib/gtkui/0000775000175000017500000000000011754440541014756 5ustar chrischrisautokey-0.90.4/config/ubuntu-mono-light/0000775000175000017500000000000011754440541017140 5ustar chrischrisautokey-0.90.4/config/Humanity/0000775000175000017500000000000011754440541015341 5ustar chrischrisautokey-0.90.4/config/ubuntu-mono-dark/0000775000175000017500000000000011754440541016752 5ustar chrischrisautokey-0.90.4/doc/man/0000775000175000017500000000000011754440541013616 5ustar chrischrisautokey-0.90.4/doc/scripting/0000775000175000017500000000000011754440541015045 5ustar chrischrisautokey-0.90.4/src/lib/0000775000175000017500000000000011754440541013633 5ustar chrischrisautokey-0.90.4/src/test/0000775000175000017500000000000011754440541014044 5ustar chrischrisautokey-0.90.4/config/0000775000175000017500000000000011754440541013543 5ustar chrischrisautokey-0.90.4/doc/0000775000175000017500000000000011754440541013043 5ustar chrischrisautokey-0.90.4/uic/0000775000175000017500000000000011754440541013056 5ustar chrischrisautokey-0.90.4/debian/0000775000175000017500000000000011754441162013520 5ustar chrischrisautokey-0.90.4/src/0000775000175000017500000000000011754440541013065 5ustar chrischrisautokey-0.90.4/0000775000175000017500000000000011754441041012272 5ustar chrischris