#65 UI improvements

meta
sezanzeb 3 years ago
parent 533381b3ac
commit 60c8367bf4

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

@ -2,11 +2,21 @@
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.22"/>
<object class="GtkImage" id="about-icon">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">help-about</property>
</object>
<object class="GtkImage" id="check-icon">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">dialog-ok</property>
</object>
<object class="GtkImage" id="copy-icon">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">edit-copy</property>
</object>
<object class="GtkImage" id="delete-icon">
<property name="visible">True</property>
<property name="can-focus">False</property>
@ -131,12 +141,12 @@
<action-widget response="-7">close_error_dialog</action-widget>
</action-widgets>
</object>
<object class="GtkImage" id="gtk-delete-icon">
<object class="GtkImage" id="gtk-delete-icon1">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">gtk-delete</property>
</object>
<object class="GtkDialog" id="unsaved_changes">
<object class="GtkDialog" id="confirm-delete">
<property name="can-focus">False</property>
<property name="border-width">4</property>
<property name="title" translatable="yes">Key Mapper</property>
@ -154,18 +164,18 @@
<object class="GtkButtonBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">center</property>
<property name="halign">end</property>
<property name="margin-top">10</property>
<property name="layout-style">end</property>
<child>
<object class="GtkButton" id="go_back">
<property name="label">Continue</property>
<object class="GtkButton" id="go_back1">
<property name="label">Delete</property>
<property name="use-action-appearance">False</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="can-default">True</property>
<property name="receives-default">False</property>
<property name="image">gtk-delete-icon</property>
<property name="image">gtk-delete-icon1</property>
</object>
<packing>
<property name="expand">False</property>
@ -174,7 +184,7 @@
</packing>
</child>
<child>
<object class="GtkButton" id="go_ahead">
<object class="GtkButton" id="go_ahead1">
<property name="label" translatable="yes">Go Back</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
@ -199,9 +209,10 @@
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkImage" id="error-image1">
<object class="GtkImage" id="error-image2">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-right">10</property>
<property name="margin-end">10</property>
<property name="yalign">0</property>
<property name="icon-name">dialog-warning</property>
@ -214,13 +225,15 @@
</packing>
</child>
<child>
<object class="GtkLabel" id="secondary_error_label1">
<object class="GtkLabel" id="secondary_error_label2">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-left">10</property>
<property name="margin-right">10</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="ypad">6</property>
<property name="label" translatable="yes">You have got unsaved changes!</property>
<property name="label" translatable="yes">Are you sure to delete your preset?</property>
<property name="use-markup">True</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
@ -241,8 +254,8 @@
</object>
</child>
<action-widgets>
<action-widget response="-3">go_back</action-widget>
<action-widget response="-6">go_ahead</action-widget>
<action-widget response="-3">go_back1</action-widget>
<action-widget response="-6">go_ahead1</action-widget>
</action-widgets>
</object>
<object class="GtkImage" id="gtk-redo-icon">
@ -353,6 +366,23 @@ To give your keys back their original mapping.</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="about">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="halign">end</property>
<property name="image">about-icon</property>
<property name="always-show-image">True</property>
<signal name="clicked" handler="on_about_clicked" swapped="no"/>
<accelerator key="Delete" signal="activate" modifiers="GDK_SHIFT_MASK"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@ -426,15 +456,15 @@ Don't hold down any keys while the injection starts.</property>
</packing>
</child>
<child>
<object class="GtkButton" id="save_preset">
<property name="label">Save</property>
<object class="GtkButton" id="copy_preset">
<property name="label">Copy</property>
<property name="width-request">80</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="image">save-icon</property>
<property name="image">copy-icon</property>
<property name="always-show-image">True</property>
<signal name="clicked" handler="on_save_preset_clicked" swapped="no"/>
<signal name="clicked" handler="on_copy_preset_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
@ -449,7 +479,6 @@ Don't hold down any keys while the injection starts.</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Hold down ctrl and click here to copy the current preset</property>
<property name="image">new-icon</property>
<property name="always-show-image">True</property>
<signal name="clicked" handler="on_create_preset_clicked" swapped="no"/>
@ -544,9 +573,36 @@ Don't hold down any keys while the injection starts.</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="preset_name_input">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkEntry" id="preset_name_input">
<property name="visible">True</property>
<property name="can-focus">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="rename-button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Save the entered name</property>
<property name="margin-start">10</property>
<property name="image">save-icon</property>
<signal name="clicked" handler="on_rename_button_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
@ -738,6 +794,7 @@ Don't hold down any keys while the injection starts.</property>
<property name="adjustment">mouse_speed_adjustment</property>
<property name="round-digits">1</property>
<property name="draw-value">False</property>
<signal name="drag-end" handler="save_preset" swapped="no"/>
<signal name="value-changed" handler="on_joystick_mouse_speed_changed" swapped="no"/>
</object>
<packing>
@ -872,7 +929,7 @@ Don't hold down any keys while the injection starts.</property>
<property name="width-request">140</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Click on a cell below and hit a key on your device. Click the "Restore Defaults" beforehand.</property>
<property name="tooltip-text" translatable="yes">Click on a cell below and hit a key on your device. Click the "Restore Defaults" button beforehand.</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="label" translatable="yes">Key</property>
@ -887,32 +944,6 @@ Don't hold down any keys while the injection starts.</property>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">"disable" disables the key outside of combinations.
Useful for turning a key into a modifier without any side effects.
Macro help:
- `r` repeats the execution of the second parameter
- `w` waits in milliseconds
- `k` writes a single keystroke
- `e` writes an event
- `m` holds a modifier while executing the second parameter
- `h` executes the parameter as long as the key is pressed down
- `.` executes two actions behind each other
- `mouse` and `wheel` take direction and speed as parameters
Macro examples:
- `k(1).k(2)` 1, 2
- `r(3, k(a).w(500))` a, a, a with 500ms pause
- `m(Control_L, k(a).k(x))` CTRL + a, CTRL + x
- `k(1).h(k(2)).k(3)` writes 1 2 2 ... 2 2 3 while the key is pressed
- `e(EV_REL, REL_X, 10)` moves the mouse cursor 10px to the right
- `mouse(right, 4)` which keeps moving the mouse while pressed
- `wheel(down, 1)` keeps scrolling down while held
Combine keycodes with `+`, for example: `control_l + a`, to write combinations
Between calls to k, key down and key up events, macros will sleep for 10ms by
default. This can be configured in ~/.config/key-mapper/config</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="label" translatable="yes">Mapping</property>
@ -997,4 +1028,634 @@ default. This can be configured in ~/.config/key-mapper/config</property>
</object>
</child>
</object>
<object class="GtkWindow" id="about-dialog">
<property name="can-focus">False</property>
<property name="icon">key-mapper.svg</property>
<property name="transient-for">window</property>
<property name="attached-to">window</property>
<child>
<object class="GtkStack" id="stack1">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">center</property>
<property name="margin-top">20</property>
<property name="margin-bottom">20</property>
<property name="orientation">vertical</property>
<property name="spacing">20</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="pixbuf">key-mapper-128.png</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="version-label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Version unknown</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="about-label">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="margin-left">10</property>
<property name="margin-right">10</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="label" translatable="yes">You can find more information and the latest version on github
&lt;a href="https://github.com/sezanzeb/key-mapper"&gt;https://github.com/sezanzeb/key-mapper&lt;/a&gt;</property>
<property name="use-markup">True</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="name">About</property>
<property name="title" translatable="yes">About</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="width-request">500</property>
<property name="height-request">300</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-left">5</property>
<property name="margin-right">5</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="border-width">10</property>
<property name="orientation">vertical</property>
<property name="spacing">10</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">A "key + key + ... + key" syntax can be used to trigger key combinations. For example "control_l + a".
"disable" disables a key.</property>
<property name="wrap">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="secondary_error_label7">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">center</property>
<property name="margin-left">10</property>
<property name="margin-right">10</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="ypad">6</property>
<property name="label" translatable="yes">Macros</property>
<property name="use-markup">True</property>
<property name="wrap">True</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Macros allow multiple characters to be written with a single key-press.</property>
<property name="wrap">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<!-- n-columns=2 n-rows=9 -->
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="column-spacing">20</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">r</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">waits in milliseconds</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">w</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">k</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">writes a single keystroke</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">e</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">holds a modifier while executing the second parameter</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">writes an event</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">m</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">repeats the execution of the second parameter</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">executes the parameter as long as the key is pressed down</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">h</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">.</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">6</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">executes two actions behind each other</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">6</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">mouse</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">7</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">wheel</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">8</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">takes direction (up, left, ...) and speed as parameters</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">7</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">same as mouse</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">8</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="secondary_error_label8">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">center</property>
<property name="margin-left">10</property>
<property name="margin-right">10</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="ypad">6</property>
<property name="label" translatable="yes">Examples</property>
<property name="use-markup">True</property>
<property name="wrap">True</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<!-- n-columns=2 n-rows=7 -->
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="column-spacing">20</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">k(1).k(2)</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">a, a, a with 500ms pause</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">r(3, k(a).w(500))</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">m(Control_L, k(a).k(x))</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">CTRL + a, CTRL + x</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">k(1).h(k(2)).k(3)</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">moves the mouse cursor 10px to the right</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">writes 1 2 2 ... 2 2 3 while the key is pressed</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">e(EV_REL, REL_X, 10)</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">1, 2</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">which keeps moving the mouse while pressed</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">mouse(right, 4)</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">wheel(down, 1)</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">6</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">keeps scrolling down while held</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">6</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="secondary_error_label9">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-right">10</property>
<property name="margin-end">10</property>
<property name="ypad">6</property>
<property name="label" translatable="yes">Between calls to k, key down and key up events, macros will sleep for 10ms by default, which can be configured in ~/.config/key-mapper/config</property>
<property name="use-markup">True</property>
<property name="wrap">True</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">6</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="name">Usage</property>
<property name="title" translatable="yes">Usage</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child type="titlebar">
<object class="GtkHeaderBar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="show-close-button">True</property>
<child type="title">
<object class="GtkStackSwitcher">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="stack">stack1</property>
</object>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
</interface>

@ -208,8 +208,6 @@ class Row(Gtk.ListBoxRow):
self.key = new_key
self.highlight()
character = self.get_character()
# the character is empty and therefore the mapping is not complete
@ -223,14 +221,6 @@ class Row(Gtk.ListBoxRow):
previous_key=previous_key
)
def highlight(self):
"""Mark this row as changed."""
self.get_style_context().add_class('changed')
def unhighlight(self):
"""Mark this row as unchanged."""
self.get_style_context().remove_class('changed')
def on_character_input_change(self, _):
"""When the output character for that keycode is typed in."""
key = self.get_key()
@ -239,8 +229,6 @@ class Row(Gtk.ListBoxRow):
if character is None:
return
self.highlight()
if key is not None:
custom_mapping.change(
new_key=key,
@ -281,6 +269,7 @@ class Row(Gtk.ListBoxRow):
self.keycode_input.set_active(False)
self._state = IDLE
keycode_reader.clear()
self.window.save_preset()
def set_keycode_input_label(self, label):
"""Set the label of the keycode input."""
@ -350,6 +339,10 @@ class Row(Gtk.ListBoxRow):
'changed',
self.on_character_input_change
)
character_input.connect(
'focus-out-event',
self.window.save_preset
)
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
box.set_homogeneous(False)

@ -28,10 +28,10 @@ from gi.repository import Gtk, Gdk, GLib
from keymapper.data import get_data_path
from keymapper.paths import get_config_path, get_preset_path
from keymapper.state import custom_mapping
from keymapper.state import custom_mapping, system_mapping
from keymapper.presets import get_presets, find_newest_preset, \
delete_preset, rename_preset, get_available_preset_name
from keymapper.logger import logger
from keymapper.logger import logger, COMMIT_HASH, version, evdev_version
from keymapper.getdevices import get_devices
from keymapper.gui.row import Row, to_string
from keymapper.gui.reader import keycode_reader
@ -52,29 +52,12 @@ CTX_SAVE = 0
CTX_APPLY = 1
CTX_ERROR = 3
CTX_WARNING = 4
CTX_MAPPING = 5
CONTINUE = True
GO_BACK = False
def get_selected_row_bg():
"""Get the background color that a row is going to have when selected."""
# ListBoxRows can be selected, but either they are always selectable
# via mouse clicks and via code, or not at all. I just want to controll
# it over code. So I have to add a class and change the background color
# to act like it's selected. For this I need the right color, but
# @selected_bg_color doesn't work for every theme. So get it from
# some widget (which is deprecated according to the docs, but it works...)
row = Gtk.ListBoxRow()
row.show_all()
context = row.get_style_context()
color = context.get_background_color(Gtk.StateFlags.SELECTED)
# but this way it can be made only slightly highlighted, which is nice
color.alpha /= 4
row.destroy()
return color.to_string()
def with_selected_device(func):
"""Decorate a function to only execute if a device is selected."""
# this should only happen if no device was found at all
@ -115,6 +98,12 @@ class HandlerDisabled:
self.widget.handler_unblock_by_func(self.handler)
def on_close_about(about, _):
"""Hide the about dialog without destroying it."""
about.hide()
return True
class Window:
"""User Interface."""
def __init__(self):
@ -125,13 +114,7 @@ class Window:
css_provider = Gtk.CssProvider()
with open(get_data_path('style.css'), 'r') as file:
data = (
file.read() +
'\n.changed{background-color:' +
get_selected_row_bg() +
';}\n'
)
css_provider.load_from_data(bytes(data, encoding='UTF-8'))
css_provider.load_from_data(bytes(file.read(), encoding='UTF-8'))
Gtk.StyleContext.add_provider_for_screen(
Gdk.Screen.get_default(),
@ -145,7 +128,14 @@ class Window:
builder.connect_signals(self)
self.builder = builder
self.unsaved_changes = builder.get_object('unsaved_changes')
self.confirm_delete = builder.get_object('confirm-delete')
self.about = builder.get_object('about-dialog')
self.about.connect('delete-event', on_close_about)
self.get('version-label').set_text(
f'key-mapper {version} {COMMIT_HASH[:7]}'
f'\npython-evdev {evdev_version}' if evdev_version else ''
)
window = self.get('window')
window.show()
@ -188,16 +178,12 @@ class Window:
self.ctrl = False
self.unreleased_warn = 0
def unsaved_changes_dialog(self):
def show_confirm_delete(self):
"""Blocks until the user decided about an action."""
self.unsaved_changes.show()
response = self.unsaved_changes.run()
self.unsaved_changes.hide()
if response == Gtk.ResponseType.ACCEPT:
return CONTINUE
return GO_BACK
self.confirm_delete.show()
response = self.confirm_delete.run()
self.confirm_delete.hide()
return response
def key_press(self, _, event):
"""To execute shortcuts.
@ -257,6 +243,7 @@ class Window:
def on_close(self, *_):
"""Safely close the application."""
logger.debug('Closing window')
self.save_preset()
self.window.hide()
for timeout in self.timeouts:
GLib.source_remove(timeout)
@ -274,8 +261,9 @@ class Window:
num_maps = len(custom_mapping)
if num_rows < num_maps or num_rows > num_maps + 1:
logger.error(
f'custom_mapping contains {len(custom_mapping)} rows, '
f'but {num_rows} are displayed'
'custom_mapping contains %d rows, '
'but %d are displayed',
len(custom_mapping), num_rows
)
logger.spam(
'Mapping %s',
@ -318,8 +306,6 @@ class Window:
This will destroy unsaved changes in the custom_mapping.
"""
self.get('preset_name_input').set_text('')
device = self.selected_device
presets = get_presets(device)
@ -341,7 +327,7 @@ class Window:
for preset in presets:
preset_selection.append(preset, preset)
# and select the newest one (on the top)
# and select the newest one (on the top). triggers on_select_preset
preset_selection.set_active(0)
def clear_mapping_table(self):
@ -350,11 +336,6 @@ class Window:
key_list.forall(key_list.remove)
custom_mapping.empty()
def unhighlight_all_rows(self):
"""Remove all rows from the mappings table."""
key_list = self.get('key_list')
key_list.forall(lambda row: row.unhighlight())
def can_modify_mapping(self, *_):
"""Show a message if changing the mapping is not possible."""
if self.dbus.get_state(self.selected_device) != RUNNING:
@ -396,6 +377,8 @@ class Window:
# they have already been read.
key = keycode_reader.read()
# TODO highlight if a row for that key exists or something
# inform the currently selected row about the new keycode
row, focused = self.get_focused_row()
if key is not None:
@ -424,28 +407,45 @@ class Window:
GLib.timeout_add(100, self.show_device_mapping_status)
def show_status(self, context_id, message, tooltip=None):
"""Show a status message and set its tooltip."""
if tooltip is None:
tooltip = message
"""Show a status message and set its tooltip.
self.get('error_status_icon').hide()
self.get('warning_status_icon').hide()
If message is None, it will remove the newest message of the
given context_id.
"""
status_bar = self.get('status_bar')
if context_id == CTX_ERROR:
self.get('error_status_icon').show()
if message is None:
status_bar.remove_all(context_id)
if context_id == CTX_WARNING:
self.get('warning_status_icon').show()
if context_id in (CTX_ERROR, CTX_MAPPING):
self.get('error_status_icon').hide()
if len(message) > 55:
message = message[:52] + '...'
if context_id == CTX_WARNING:
self.get('warning_status_icon').hide()
status_bar = self.get('status_bar')
status_bar.push(context_id, message)
status_bar.set_tooltip_text(tooltip)
status_bar.set_tooltip_text('')
else:
if tooltip is None:
tooltip = message
self.get('error_status_icon').hide()
self.get('warning_status_icon').hide()
if context_id in (CTX_ERROR, CTX_MAPPING):
self.get('error_status_icon').show()
if context_id == CTX_WARNING:
self.get('warning_status_icon').show()
if len(message) > 55:
message = message[:52] + '...'
status_bar.push(context_id, message)
status_bar.set_tooltip_text(tooltip)
def check_macro_syntax(self):
"""Check if the programmed macros are allright."""
self.show_status(CTX_MAPPING, None)
for key, output in custom_mapping:
if not is_this_a_macro(output):
continue
@ -456,48 +456,43 @@ class Window:
position = to_string(key)
msg = f'Syntax error at {position}, hover for info'
self.show_status(CTX_ERROR, msg, error)
self.show_status(CTX_MAPPING, msg, error)
@with_selected_preset
def on_save_preset_clicked(self, _):
"""Save changes to a preset to the file system."""
def on_rename_button_clicked(self, _):
"""Rename the preset based on the contents of the name input."""
new_name = self.get('preset_name_input').get_text()
try:
self.save_preset()
if new_name not in ['', self.selected_preset]:
# if a new name is entered
rename_preset(
self.selected_device,
self.selected_preset,
new_name
)
# if the old preset was being autoloaded, change the
# name there as well
is_autoloaded = config.is_autoloaded(
self.selected_device,
self.selected_preset
)
if is_autoloaded:
config.set_autoload_preset(
self.selected_device,
new_name
)
# after saving the config, its modification date will be the
# newest, so populate_presets will automatically select the
# right one again.
self.populate_presets()
self.show_status(CTX_SAVE, f'Saved "{self.selected_preset}"')
self.check_macro_syntax()
if new_name in ['', self.selected_preset]:
return
except PermissionError as error:
error = str(error)
self.show_status(CTX_ERROR, 'Permission denied!', error)
logger.error(error)
self.save_preset()
new_name = rename_preset(
self.selected_device,
self.selected_preset,
new_name
)
# if the old preset was being autoloaded, change the
# name there as well
is_autoloaded = config.is_autoloaded(
self.selected_device,
self.selected_preset
)
if is_autoloaded:
config.set_autoload_preset(self.selected_device, new_name)
self.get('preset_name_input').set_text('')
self.populate_presets()
@with_selected_preset
def on_delete_preset_clicked(self, _):
"""Delete a preset from the file system."""
accept = Gtk.ResponseType.ACCEPT
if len(custom_mapping) > 0 and self.show_confirm_delete() != accept:
return
custom_mapping.changed = False
delete_preset(self.selected_device, self.selected_preset)
self.populate_presets()
@ -565,11 +560,9 @@ class Window:
def on_select_device(self, dropdown):
"""List all presets, create one if none exist yet."""
if dropdown.get_active_id() == self.selected_device:
return
self.save_preset()
if custom_mapping.changed and self.unsaved_changes_dialog() == GO_BACK:
dropdown.set_active_id(self.selected_device)
if dropdown.get_active_id() == self.selected_device:
return
# selecting a device will also automatically select a different
@ -637,13 +630,19 @@ class Window:
else:
self.get('apply_system_layout').set_opacity(0.4)
@with_selected_preset
def on_copy_preset_clicked(self, _):
"""Copy the current preset and select it."""
self.create_preset(True)
@with_selected_device
def on_create_preset_clicked(self, _):
"""Create a new preset and select it."""
if custom_mapping.changed and self.unsaved_changes_dialog() == GO_BACK:
return
self.create_preset()
copy = self.ctrl
def create_preset(self, copy=False):
"""Create a new preset and select it."""
self.save_preset()
try:
if copy:
@ -672,10 +671,6 @@ class Window:
if dropdown.get_active_id() == self.selected_preset:
return
if custom_mapping.changed and self.unsaved_changes_dialog() == GO_BACK:
dropdown.set_active_id(self.selected_preset)
return
self.clear_mapping_table()
preset = dropdown.get_active_text()
@ -713,11 +708,13 @@ class Window:
"""Set the purpose of the left joystick."""
purpose = dropdown.get_active_id()
custom_mapping.set('gamepad.joystick.left_purpose', purpose)
self.save_preset()
def on_right_joystick_changed(self, dropdown):
"""Set the purpose of the right joystick."""
purpose = dropdown.get_active_id()
custom_mapping.set('gamepad.joystick.right_purpose', purpose)
self.save_preset()
def on_joystick_mouse_speed_changed(self, gtk_range):
"""Set how fast the joystick moves the mouse."""
@ -744,16 +741,41 @@ class Window:
# https://stackoverflow.com/a/30329591/4417769
key_list.remove(single_key_mapping)
def save_preset(self):
def save_preset(self, *_):
"""Write changes to presets to disk."""
logger.info(
'Updating configs for "%s", "%s"',
self.selected_device,
self.selected_preset
)
if not custom_mapping.changed:
return
try:
path = get_preset_path(self.selected_device, self.selected_preset)
custom_mapping.save(path)
path = get_preset_path(self.selected_device, self.selected_preset)
custom_mapping.save(path)
custom_mapping.changed = False
custom_mapping.changed = False
self.unhighlight_all_rows()
# after saving the config, its modification date will be the
# newest, so populate_presets will automatically select the
# right one again.
self.populate_presets()
except PermissionError as error:
error = str(error)
self.show_status(CTX_ERROR, 'Permission denied!', error)
logger.error(error)
for _, character in custom_mapping:
if is_this_a_macro(character):
continue
if system_mapping.get(character) is None:
self.show_status(CTX_MAPPING, f'Unknown mapping "{character}"')
break
else:
# no broken mappings found
self.show_status(CTX_MAPPING, None)
# checking macros is probably a bit more expensive, do that if
# the regular mappings are allright
self.check_macro_syntax()
def on_about_clicked(self, _):
"""Show the about/help dialog."""
self.about.show()

@ -295,13 +295,12 @@ class Injector(multiprocessing.Process):
loop.stop()
return
def get_udef_name(self, name, prefix):
def get_udef_name(self, name, suffix):
"""Make sure the generated name is not longer than 80 chars."""
max_len = 80 # based on error messages
suffix = 'key-mapper'
remaining_len = max_len - len(suffix) - len(prefix) - 2
remaining_len = max_len - len(DEV_NAME) - len(suffix) - 2
middle = name[:remaining_len]
name = f'{suffix} {middle} {prefix}'
name = f'{DEV_NAME} {middle} {suffix}'
return name
def run(self):

@ -267,8 +267,6 @@ class _Macro:
if code is None:
raise KeyError(f'Unknown key "{character}"')
if EV_KEY not in self.capabilities:
self.capabilities[EV_KEY] = set()
self.capabilities[EV_KEY].add(code)
self.tasks.append(lambda handler: handler(EV_KEY, code, 1))

@ -34,7 +34,7 @@ start = time.time()
previous_key_spam = None
COMMIT_HASH = '' # overwritten in setup.py
COMMIT_HASH = '12ff3df22e47a2e8b7be2811b300512c2d597725' # overwritten in setup.py
def spam(self, message, *args, **kwargs):
@ -140,6 +140,17 @@ logger.setLevel(logging.INFO)
logging.getLogger('asyncio').setLevel(logging.WARNING)
logger.main_pid = os.getpid()
try:
name = pkg_resources.require('key-mapper')[0].project_name
version = pkg_resources.require('key-mapper')[0].version
evdev_version = pkg_resources.require('evdev')[0].version
except pkg_resources.DistributionNotFound as error:
name = 'key-mapper'
version = ''
evdev_version = None
logger.info('Could not figure out the version')
logger.debug(error)
def is_debug():
"""True, if the logger is currently in DEBUG or SPAM mode."""
@ -149,19 +160,13 @@ def is_debug():
def log_info():
"""Log version and name to the console"""
# read values from setup.py
try:
name = pkg_resources.require('key-mapper')[0].project_name
version = pkg_resources.require('key-mapper')[0].version
logger.info(
'%s %s %s https://github.com/sezanzeb/key-mapper',
name, version, COMMIT_HASH
)
logger.info(
'%s %s %s https://github.com/sezanzeb/key-mapper',
name, version, COMMIT_HASH
)
evdev_version = pkg_resources.require('evdev')[0].version
if evdev_version:
logger.info('python-evdev %s', evdev_version)
except pkg_resources.DistributionNotFound as error:
logger.info('Could not figure out the version')
logger.debug(error)
if is_debug():
logger.warning(

@ -178,7 +178,7 @@ def delete_preset(device, preset):
def rename_preset(device, old_preset_name, new_preset_name):
"""Rename one of the users presets while avoiding name conflicts."""
if new_preset_name == old_preset_name:
return
return None
new_preset_name = get_available_preset_name(device, new_preset_name)
logger.info('Moving "%s" to "%s"', old_preset_name, new_preset_name)
@ -189,3 +189,4 @@ def rename_preset(device, old_preset_name, new_preset_name):
# set the modification date to now
now = time.time()
os.utime(get_preset_path(device, new_preset_name), (now, now))
return new_preset_name

@ -100,7 +100,6 @@ Bear in mind that anti-cheat software might detect macros in games.
## UI Shortcuts
- Hold down `ctrl` and click on "new" to copy the current preset
- `shift` + `del` stops the injection (only works while the gui is in focus)
- `ctrl` + `q` closes the application

@ -80,8 +80,6 @@ def launch(argv=None):
gtk_iteration()
module.window.unsaved_changes.run = lambda: Gtk.ResponseType.ACCEPT
return module.window
@ -340,7 +338,6 @@ class TestIntegration(unittest.TestCase):
row = rows[-1]
self.assertIsNone(row.get_key())
self.assertEqual(row.character_input.get_text(), '')
self.assertNotIn('changed', row.get_style_context().list_classes())
self.assertEqual(row._state, IDLE)
if char and not code_first:
@ -391,8 +388,6 @@ class TestIntegration(unittest.TestCase):
if expect_success:
self.assertEqual(row.get_key(), key)
css_classes = row.get_style_context().list_classes()
self.assertIn('changed', css_classes)
self.assertEqual(row.keycode_input.get_label(), to_string(key))
self.assertFalse(row.keycode_input.is_focus())
self.assertEqual(len(keycode_reader._unreleased), 0)
@ -400,8 +395,6 @@ class TestIntegration(unittest.TestCase):
if not expect_success:
self.assertIsNone(row.get_key())
self.assertIsNone(row.get_character())
css_classes = row.get_style_context().list_classes()
self.assertNotIn('changed', css_classes)
self.assertEqual(row._state, IDLE)
# it won't switch the focus to the character input
self.assertTrue(row.keycode_input.is_focus())
@ -463,23 +456,14 @@ class TestIntegration(unittest.TestCase):
"""save"""
self.window.on_save_preset_clicked(None)
for row in self.get_rows():
css_classes = row.get_style_context().list_classes()
self.assertNotIn('changed', css_classes)
# unfocusing the row triggers saving the preset
self.window.window.set_focus(None)
self.assertFalse(custom_mapping.changed)
"""edit first row"""
# now change the first row and it should turn blue,
# but the other should remain unhighlighted
row = self.get_rows()[0]
row.character_input.set_text('c')
self.assertIn('changed', row.get_style_context().list_classes())
for row in self.get_rows()[1:]:
css_classes = row.get_style_context().list_classes()
self.assertNotIn('changed', css_classes)
self.assertEqual(custom_mapping.get_character(ev_1), 'c')
self.assertEqual(custom_mapping.get_character(ev_2), 'k(b).k(c)')
@ -509,7 +493,6 @@ class TestIntegration(unittest.TestCase):
self.assertEqual(custom_mapping.get_character(ev_2), 'b')
self.assertEqual(custom_mapping.get_character(ev_3), 'c')
self.assertEqual(custom_mapping.get_character(ev_4), 'd')
self.assertTrue(custom_mapping.changed)
# and trying to add them as duplicate rows will be ignored for each
# of them
@ -522,7 +505,6 @@ class TestIntegration(unittest.TestCase):
self.assertEqual(custom_mapping.get_character(ev_2), 'b')
self.assertEqual(custom_mapping.get_character(ev_3), 'c')
self.assertEqual(custom_mapping.get_character(ev_4), 'd')
self.assertTrue(custom_mapping.changed)
def test_combination(self):
# it should be possible to write a key combination
@ -667,14 +649,15 @@ class TestIntegration(unittest.TestCase):
custom_mapping.change(Key(EV_KEY, 14, 1), 'a', None)
self.assertEqual(self.window.selected_preset, 'new preset')
self.window.on_save_preset_clicked(None)
self.window.save_preset()
self.assertEqual(custom_mapping.get_character(Key(EV_KEY, 14, 1)), 'a')
config.set_autoload_preset('device 1', 'new preset')
self.assertTrue(config.is_autoloaded('device 1', 'new preset'))
custom_mapping.change(Key(EV_KEY, 14, 1), 'b', None)
self.window.get('preset_name_input').set_text('asdf')
self.window.on_save_preset_clicked(None)
self.window.save_preset()
self.window.on_rename_button_clicked(None)
self.assertEqual(self.window.selected_preset, 'asdf')
self.assertTrue(os.path.exists(f'{CONFIG_PATH}/presets/device 1/asdf.json'))
self.assertEqual(custom_mapping.get_character(Key(EV_KEY, 14, 1)), 'b')
@ -682,9 +665,6 @@ class TestIntegration(unittest.TestCase):
self.assertTrue(config.is_autoloaded('device 1', 'asdf'))
error_icon = self.window.get('error_status_icon')
status = self.window.get('status_bar')
tooltip = status.get_tooltip_text().lower()
self.assertIn('saved', tooltip)
self.assertFalse(error_icon.get_visible())
def test_rename_and_create(self):
@ -692,12 +672,73 @@ class TestIntegration(unittest.TestCase):
# start with "new preset" again
custom_mapping.change(Key(EV_KEY, 14, 1), 'a', None)
self.window.get('preset_name_input').set_text('asdf')
self.window.on_save_preset_clicked(None)
self.window.save_preset()
self.window.on_rename_button_clicked(None)
self.assertEqual(len(custom_mapping), 1)
self.assertEqual(self.window.selected_preset, 'asdf')
self.window.on_create_preset_clicked(None)
self.assertEqual(self.window.selected_preset, 'new preset')
self.assertIsNone(custom_mapping.get_character(Key(EV_KEY, 14, 1)))
config.set_autoload_preset('device 1', 'new preset')
# renaming another preset to an existing name appends a number
self.window.get('preset_name_input').set_text('asdf')
self.window.on_rename_button_clicked(None)
self.assertEqual(self.window.selected_preset, 'asdf 2')
# and that added number is correctly used in the autoload
# configuration as well
self.assertTrue(config.is_autoloaded('device 1', 'asdf 2'))
def test_avoids_redundant_saves(self):
custom_mapping.change(Key(EV_KEY, 14, 1), 'abcd', None)
custom_mapping.changed = False
self.window.save_preset()
with open(get_preset_path('device 1', 'new preset')) as f:
content = f.read()
self.assertNotIn('abcd', content)
custom_mapping.changed = True
self.window.save_preset()
with open(get_preset_path('device 1', 'new preset')) as f:
content = f.read()
self.assertIn('abcd', content)
def test_check_for_unknown_characters(self):
status = self.window.get('status_bar')
error_icon = self.window.get('error_status_icon')
warning_icon = self.window.get('warning_status_icon')
custom_mapping.change(Key(EV_KEY, 71, 1), 'qux', None)
custom_mapping.change(Key(EV_KEY, 72, 1), 'foo', None)
self.window.save_preset()
tooltip = status.get_tooltip_text().lower()
self.assertIn('qux', tooltip)
self.assertTrue(error_icon.get_visible())
self.assertFalse(warning_icon.get_visible())
# it will still save it though
with open(get_preset_path('device 1', 'new preset')) as f:
content = f.read()
self.assertIn('qux', content)
self.assertIn('foo', content)
custom_mapping.change(Key(EV_KEY, 71, 1), 'a', None)
self.window.save_preset()
tooltip = status.get_tooltip_text().lower()
self.assertIn('foo', tooltip)
self.assertTrue(error_icon.get_visible())
self.assertFalse(warning_icon.get_visible())
custom_mapping.change(Key(EV_KEY, 72, 1), 'b', None)
self.window.save_preset()
tooltip = status.get_tooltip_text()
self.assertIsNone(tooltip)
self.assertFalse(error_icon.get_visible())
self.assertFalse(warning_icon.get_visible())
def test_check_macro_syntax(self):
status = self.window.get('status_bar')
@ -705,17 +746,16 @@ class TestIntegration(unittest.TestCase):
warning_icon = self.window.get('warning_status_icon')
custom_mapping.change(Key(EV_KEY, 9, 1), 'k(1))', None)
self.window.on_save_preset_clicked(None)
self.window.save_preset()
tooltip = status.get_tooltip_text().lower()
self.assertIn('brackets', tooltip)
self.assertTrue(error_icon.get_visible())
self.assertFalse(warning_icon.get_visible())
custom_mapping.change(Key(EV_KEY, 9, 1), 'k(1)', None)
self.window.on_save_preset_clicked(None)
tooltip = status.get_tooltip_text().lower()
self.window.save_preset()
tooltip = (status.get_tooltip_text() or '').lower()
self.assertNotIn('brackets', tooltip)
self.assertIn('saved', tooltip)
self.assertFalse(error_icon.get_visible())
self.assertFalse(warning_icon.get_visible())
@ -750,7 +790,8 @@ class TestIntegration(unittest.TestCase):
self.assertEqual(self.window.selected_preset, 'new preset')
self.assertFalse(os.path.exists(f'{CONFIG_PATH}/presets/device 1/abc 123.json'))
custom_mapping.change(Key(EV_KEY, 10, 1), '1', None)
self.window.on_save_preset_clicked(None)
self.window.save_preset()
self.window.on_rename_button_clicked(None)
gtk_iteration()
self.assertEqual(self.window.selected_preset, 'abc 123')
self.assertTrue(os.path.exists(f'{CONFIG_PATH}/presets/device 1/abc 123.json'))
@ -768,10 +809,9 @@ class TestIntegration(unittest.TestCase):
self.change_empty_row(Key(EV_KEY, 81, 1), 'a')
time.sleep(0.1)
gtk_iteration()
self.window.on_save_preset_clicked(None)
self.window.save_preset()
self.assertEqual(len(key_list.get_children()), 2)
self.window.ctrl = False
self.window.on_create_preset_clicked(None)
# the preset should be empty, only one empty row present
@ -781,21 +821,21 @@ class TestIntegration(unittest.TestCase):
self.change_empty_row(Key(EV_KEY, 81, 1), 'b')
time.sleep(0.1)
gtk_iteration()
self.window.on_save_preset_clicked(None)
self.window.save_preset()
self.assertEqual(len(key_list.get_children()), 2)
# this time it should be copied
self.window.ctrl = True
self.window.on_create_preset_clicked(None)
self.window.on_copy_preset_clicked(None)
self.assertEqual(self.window.selected_preset, 'new preset 2 copy')
self.assertEqual(len(key_list.get_children()), 2)
self.assertEqual(key_list.get_children()[0].get_character(), 'b')
# make another copy
self.window.on_create_preset_clicked(None)
self.window.on_copy_preset_clicked(None)
self.assertEqual(self.window.selected_preset, 'new preset 2 copy 2')
self.assertEqual(len(key_list.get_children()), 2)
self.assertEqual(key_list.get_children()[0].get_character(), 'b')
self.assertEqual(len(custom_mapping), 1)
def test_gamepad_config(self):
# set some stuff in the beginning, otherwise gtk fails to
@ -1016,7 +1056,7 @@ class TestIntegration(unittest.TestCase):
] * 100
custom_mapping.change(Key(EV_ABS, ABS_X, 1), 'a')
self.window.on_save_preset_clicked(None)
self.window.save_preset()
gtk_iteration()
@ -1076,6 +1116,28 @@ class TestIntegration(unittest.TestCase):
write_history.append(pipe.recv())
self.assertEqual(len(write_history), len_before)
def test_delete_preset(self):
custom_mapping.change(Key(EV_KEY, 71, 1), 'a', None)
self.window.get('preset_name_input').set_text('asdf')
self.window.on_rename_button_clicked(None)
gtk_iteration()
self.assertEqual(self.window.selected_preset, 'asdf')
self.assertEqual(len(custom_mapping), 1)
self.window.save_preset()
self.assertTrue(os.path.exists(get_preset_path('device 1', 'asdf')))
with patch.object(self.window, 'show_confirm_delete', lambda: Gtk.ResponseType.CANCEL):
self.window.on_delete_preset_clicked(None)
self.assertTrue(os.path.exists(get_preset_path('device 1', 'asdf')))
self.assertEqual(self.window.selected_preset, 'asdf')
self.assertEqual(self.window.selected_device, 'device 1')
with patch.object(self.window, 'show_confirm_delete', lambda: Gtk.ResponseType.ACCEPT):
self.window.on_delete_preset_clicked(None)
self.assertFalse(os.path.exists(get_preset_path('device 1', 'asdf')))
self.assertEqual(self.window.selected_preset, 'new preset')
self.assertEqual(self.window.selected_device, 'device 1')
original_access = os.access
original_getgrnam = grp.getgrnam

@ -26,7 +26,7 @@ import asyncio
from evdev.ecodes import EV_REL, EV_KEY, REL_Y, REL_X, REL_WHEEL, REL_HWHEEL
from keymapper.injection.macros import parse, _Macro, _extract_params, \
is_this_a_macro, _parse_recurse, handle_plus_syntax
is_this_a_macro, _parse_recurse, handle_plus_syntax, _count_brackets
from keymapper.config import config
from keymapper.mapping import Mapping
from keymapper.state import system_mapping
@ -60,6 +60,8 @@ class TestMacros(unittest.TestCase):
self.assertFalse(is_this_a_macro('btn_left'))
self.assertFalse(is_this_a_macro('minus'))
self.assertFalse(is_this_a_macro('k'))
self.assertFalse(is_this_a_macro(1))
self.assertFalse(is_this_a_macro(None))
self.assertTrue(is_this_a_macro('a+b'))
self.assertTrue(is_this_a_macro('a+b+c'))
@ -153,7 +155,8 @@ class TestMacros(unittest.TestCase):
self.assertEqual(len(macro.child_macros), 0)
def test_1(self):
macro = parse('k(1).k(a).k(3)', self.mapping)
# quotation marks are removed automatically and don't do any harm
macro = parse('k(1).k("a").k(3)', self.mapping)
self.assertSetEqual(macro.get_capabilities()[EV_KEY], {
system_mapping.get('1'),
system_mapping.get('a'),
@ -197,6 +200,14 @@ class TestMacros(unittest.TestCase):
self.assertIsNotNone(error)
error = parse('r(1, k(1))', self.mapping, return_errors=True)
self.assertIsNone(error)
error = parse('m(asdf, k(a))', self.mapping, return_errors=True)
self.assertIsNotNone(error)
error = parse('h(a)', self.mapping, return_errors=True)
self.assertIn('macro', error)
self.assertIn('a', error)
error = parse('foo(a)', self.mapping, return_errors=True)
self.assertIn('unknown', error.lower())
self.assertIn('foo', error)
def test_hold(self):
macro = parse('k(1).h(k(a)).k(3)', self.mapping)
@ -207,6 +218,10 @@ class TestMacros(unittest.TestCase):
})
macro.press_key()
self.loop.run_until_complete(asyncio.sleep(0.05))
self.assertTrue(macro.is_holding())
macro.press_key() # redundantly calling doesn't break anything
asyncio.ensure_future(macro.run(self.handler))
self.loop.run_until_complete(asyncio.sleep(0.2))
self.assertTrue(macro.is_holding())
@ -493,7 +508,7 @@ class TestMacros(unittest.TestCase):
self.assertEqual(len(macro.child_macros), 0)
def test_event_2(self):
macro = parse('e(5421, 324, 154)', self.mapping)
macro = parse('r(1, e(5421, 324, 154))', self.mapping)
code = 324
self.assertSetEqual(macro.get_capabilities()[5421], {324})
self.assertSetEqual(macro.get_capabilities()[EV_REL], set())
@ -501,7 +516,17 @@ class TestMacros(unittest.TestCase):
self.loop.run_until_complete(macro.run(self.handler))
self.assertListEqual(self.result, [(5421, code, 154)])
self.assertEqual(len(macro.child_macros), 0)
self.assertEqual(len(macro.child_macros), 1)
def test_count_brackets(self):
self.assertEqual(_count_brackets(''), 0)
self.assertEqual(_count_brackets('()'), 2)
self.assertEqual(_count_brackets('a()'), 3)
self.assertEqual(_count_brackets('a(b)'), 4)
self.assertEqual(_count_brackets('a(b())'), 6)
self.assertEqual(_count_brackets('a(b(c))'), 7)
self.assertEqual(_count_brackets('a(b(c))d'), 7)
self.assertEqual(_count_brackets('a(b(c))d()'), 7)
if __name__ == '__main__':

Loading…
Cancel
Save