35 Commits

Author SHA1 Message Date
Storm Dragon 4c8c8d896d Fixed version conflict. 2025-06-05 16:05:11 -04:00
Storm Dragon bd151c7cec Removed the speakup keyboard layout. It was never finished, and Fenrir has more functionality anyway and isn't that hard to learn. 2025-06-04 22:22:51 -04:00
Storm Dragon 6f4784daed Pty layouts skipped in keyboard layout cycling. 2025-06-04 22:14:49 -04:00
Storm Dragon 8c471adfa4 Keyboard layout shortcut changed to a single key, f4, because it multikey shortcuts require the fenrir key. 2025-06-04 20:37:21 -04:00
Storm Dragon 77065c55b4 Attempt to add keyboard shortcut to switch keyboard layout. 2025-06-04 20:17:06 -04:00
Storm Dragon 7f75c231e1 Removed _ from some punctuation, added it to most. 2025-06-04 19:34:15 -04:00
Storm Dragon 4672592dba Latest merge from testing. 2025-04-28 15:41:14 -04:00
Storm Dragon 6b84a8e9bc Rename suspendingScreen to ignoreScreen. Requires edit to your settings file, find suspending and change it to ignore. This may break stuff, please let me know. 2025-04-23 01:36:03 -04:00
Storm Dragon 3212962a5b Adjusted a few of the defaults in punctuation and in settings file. Logs are now in a more screen reader friendly format with timestamps and level coming after the actual information. 2025-04-21 14:04:57 -04:00
Storm Dragon 9e9cb883e7 Updated requirements.txt. 2025-04-17 01:04:38 -04:00
Storm Dragon 7a12992b88 latest release. 2025-04-17 00:36:26 -04:00
Storm Dragon 26a8c8cf86 Added setproctitle to dependencies. 2025-04-17 00:35:02 -04:00
Storm Dragon bbfd2790a9 Attempt to fix import/export GUI clipboard for some distros. 2025-04-16 21:50:37 -04:00
Storm Dragon 6edb743c23 Removed rapidfuzz from dependencies. 2025-04-16 12:14:30 -04:00
Storm Dragon a1fcee9a45 Removed the rapidfuzz dependency. There wasn't much of a gain in performance and it did not work on some distros. 2025-04-16 12:06:54 -04:00
Storm Dragon 4e6e6e2d17 Fixed a couple of bugs in setup.py. 2025-04-16 00:52:44 -04:00
Storm Dragon 7a87fb51bb Fixed version for master branch. 2025-04-14 20:04:14 -04:00
Storm Dragon 676c2b07a9 A couple of small improvements to install.sh. 2025-04-14 20:02:00 -04:00
Storm Dragon 2dda73ac87 Request to be able to use the numpad if numlock is on and only process fenrir commands if numlock is off. This should work, let me know if anything breaks. 2025-04-14 18:57:10 -04:00
Storm Dragon f68a1af223 Fixed a typo in requirements.txt. 2025-04-10 05:46:22 -04:00
Storm Dragon 5ab66f6978 Attempt to fix the bug where Fenrir freezes the computer solid if it manages to start before sound is ready. It shuld at least release the keyboard now. 2025-03-20 02:55:39 -04:00
Storm Dragon 2cc2fda28c Actually fix the version file this time. 2025-03-02 17:59:20 -05:00
Storm Dragon c99d0f6ee1 Fixed version.py. 2025-03-02 17:44:32 -05:00
Storm Dragon 1552b962a1 Updated dependencies. 2025-03-02 17:43:01 -05:00
Storm Dragon 09391bfe84 Experimental fix for evdev failures. 2025-03-02 17:24:45 -05:00
Storm Dragon e76ca9889a Same update for export to x clipboard. Now using pyperclip. 2025-03-02 16:04:38 -05:00
Storm Dragon 73206ce393 Switched from xclip to pyperclip for import from x. 2025-03-02 15:25:06 -05:00
Storm Dragon 5b642cd9e2 Fixed error in settings file. 2025-02-26 17:41:01 -05:00
Storm Dragon 4966b87ba1 pyttsx removed from setup file because it's no longer a speech option. 2025-02-26 17:38:22 -05:00
Storm Dragon 145cab6221 Updated dependencies to include rapidfuzz. 2025-02-26 17:08:50 -05:00
Storm Dragon e46926f145 Fixed a traceback on shutdown. Hopefully improved responsiveness with the diff. Trying rapidfuzz for smaller screen updates, add a catch to fall back to the original difflib if there are any problems. This is experimental, please watch for bugs. 2025-02-26 17:02:25 -05:00
Storm Dragon 8cd50c5070 Hopefully improve accuracy of blank line reporting. 2025-02-26 16:05:17 -05:00
Storm Dragon ed918a0334 Forgot to push one of the punctuation files.
?
2025-01-08 20:26:46 -05:00
Storm Dragon 760b873abe Updated emoticon handling to make it more accurate, fewer false positives. 2025-01-04 21:57:12 -05:00
Storm Dragon 46c348634c Post release switch code name back to master. 2024-12-21 18:01:42 -05:00
39 changed files with 684 additions and 616 deletions
+1
View File
@@ -126,3 +126,4 @@ KEY_FENRIR,KEY_F8=export_clipboard_to_x
KEY_FENRIR,KEY_CTRL,KEY_UP=inc_alsa_volume KEY_FENRIR,KEY_CTRL,KEY_UP=inc_alsa_volume
KEY_FENRIR,KEY_CTRL,KEY_DOWN=dec_alsa_volume KEY_FENRIR,KEY_CTRL,KEY_DOWN=dec_alsa_volume
KEY_FENRIR,KEY_SHIFT,KEY_V=announce_fenrir_version KEY_FENRIR,KEY_SHIFT,KEY_V=announce_fenrir_version
KEY_F4=cycle_keyboard_layout
+1
View File
@@ -126,3 +126,4 @@ KEY_FENRIR,KEY_F8=export_clipboard_to_x
KEY_FENRIR,KEY_CTRL,KEY_UP=inc_alsa_volume KEY_FENRIR,KEY_CTRL,KEY_UP=inc_alsa_volume
KEY_FENRIR,KEY_CTRL,KEY_DOWN=dec_alsa_volume KEY_FENRIR,KEY_CTRL,KEY_DOWN=dec_alsa_volume
KEY_FENRIR,KEY_SHIFT,KEY_V=announce_fenrir_version KEY_FENRIR,KEY_SHIFT,KEY_V=announce_fenrir_version
KEY_F4=cycle_keyboard_layout
+1
View File
@@ -126,3 +126,4 @@ KEY_FENRIR,KEY_CTRL,KEY_C=save_settings
KEY_FENRIR,KEY_F8=export_clipboard_to_x KEY_FENRIR,KEY_F8=export_clipboard_to_x
KEY_FENRIR,KEY_ALT,KEY_UP=inc_alsa_volume KEY_FENRIR,KEY_ALT,KEY_UP=inc_alsa_volume
KEY_FENRIR,KEY_ALT,KEY_DOWN=dec_alsa_volume KEY_FENRIR,KEY_ALT,KEY_DOWN=dec_alsa_volume
KEY_F4=cycle_keyboard_layout
+1
View File
@@ -126,3 +126,4 @@ KEY_FENRIR,KEY_CTRL,KEY_C=save_settings
KEY_FENRIR,KEY_F8=export_clipboard_to_x KEY_FENRIR,KEY_F8=export_clipboard_to_x
KEY_FENRIR,KEY_ALT,KEY_UP=inc_alsa_volume KEY_FENRIR,KEY_ALT,KEY_UP=inc_alsa_volume
KEY_FENRIR,KEY_ALT,KEY_DOWN=dec_alsa_volume KEY_FENRIR,KEY_ALT,KEY_DOWN=dec_alsa_volume
KEY_F4=cycle_keyboard_layout
+2
View File
@@ -85,3 +85,5 @@ alt+f12 - quit fenrir
^[[1;3F=temp_disable_speech ^[[1;3F=temp_disable_speech
# control+end - toggle auto read # control+end - toggle auto read
^[[1;5F=toggle_auto_read ^[[1;5F=toggle_auto_read
# F12 - cycle keyboard layout
^[[24~=cycle_keyboard_layout
-218
View File
@@ -1,218 +0,0 @@
# Fenrir comment: copy of speakup DefaultKeyAssignments converted to fenrir syntax
# Fenrir comment: https://android.googlesource.com/kernel/msm/+/android-7.1.0_r0.2/drivers/staging/speakup/DefaultKeyAssignments
# Fenrir comment: The insert or shift key named below is the fenrir key
# This file is intended to give you an overview of the default keys used
# by speakup for it's review functions. You may change them to be
# anything you want but that will take some familiarity with key
# mapping.
# We have remapped the insert or zero key on the keypad to act as a
# shift key. Well, actually as an altgr key. So in the following list
# InsKeyPad-period means hold down the insert key like a shift key and
# hit the keypad period.
# KeyPad-8 Say current Line
KEY_KP8=review_curr_line
# InsKeyPad-8 say from top of screen to reading cursor.
KEY_FENRIR,KEY_KP8=curr_screen_before_cursor
# KeyPad-7 Say Previous Line (UP one line)
KEY_KP7=review_prev_line
# KeyPad-9 Say Next Line (down one line)
KEY_KP9=review_next_line
# KeyPad-5 Say Current Word
KEY_KP5=review_curr_word
# InsKeyPad-5 Spell Current Word
KEY_FENRIR,KEY_KP5=review_curr_word_phonetic
# KeyPad-4 Say Previous Word (left one word)
KEY_KP4=review_prev_word
# InsKeyPad-4 say from left edge of line to reading cursor.
KEY_FENRIR,KEY_KP4=cursor_read_line_to_cursor
# KeyPad-6 Say Next Word (right one word)
KEY_KP6=review_next_word
# InsKeyPad-6 Say from reading cursor to right edge of line.
KEY_FENRIR,KEY_KP6=cursor_read_to_end_of_line
# KeyPad-2 Say Current Letter
KEY_KP2=review_curr_char
# InsKeyPad-2 say current letter phonetically
KEY_FENRIR,KEY_KP2=review_curr_char_phonetic
# KeyPad-1 Say Previous Character (left one letter)
KEY_KP1=review_prev_char
# KeyPad-3 Say Next Character (right one letter)
KEY_KP3=review_next_char
# KeyPad-plus Say Entire Screen
KEY_KPPLUS=curr_screen
# InsKeyPad-plus Say from reading cursor line to bottom of screen.
KEY_FENRIR,KEY_KPPLUS=curr_screen_after_cursor
# KeyPad-Minus Park reading cursor (toggle)
# TODO
# InsKeyPad-minus Say character hex and decimal value.
# TODO
# KeyPad-period Say Position (current line, position and console)
KEY_KPDOT=cursor_position
# InsKeyPad-period say colour attributes of current position.
KEY_FENRIR,KEY_KPDOT=attribute_cursor
# InsKeyPad-9 Move reading cursor to top of screen (insert pgup)
KEY_FENRIR,KEY_KP9=review_bottom
# InsKeyPad-3 Move reading cursor to bottom of screen (insert pgdn)
KEY_FENRIR,KEY_KP3=review_top
# InsKeyPad-7 Move reading cursor to left edge of screen (insert home)
KEY_FENRIR,KEY_KP7=review_screen_first_char
# InsKeyPad-1 Move reading cursor to right edge of screen (insert end)
KEY_FENRIR,KEY_KP1=review_screen_last_char
# ControlKeyPad-1 Move reading cursor to last character on current line.
KEY_CTRL,KEY_KP1=review_line_end
# KeyPad-Enter Shut Up (until another key is hit) and sync reading cursor
KEY_KPENTER=temp_disable_speech
# InsKeyPad-Enter Shut Up (until toggled back on).
KEY_FENRIR,KEY_KPENTER=toggle_speech
# InsKeyPad-star n<x|y> go to line (y) or column (x). Where 'n' is any
# allowed value for the row or column for your current screen.
# TODO
# KeyPad-/ Mark and Cut screen region.
KEY_KPSLASH=copy_marked_to_clipboard
# InsKeyPad-/ Paste screen region into any console.
KEY_FENRIR,KEY_KPSLASH=paste_clipboard
# Hitting any key while speakup is outputting speech will quiet the
# synth until it has caught up with what is being printed on the
# console.
# following by other fenrir commands
KEY_FENRIR,KEY_H=toggle_tutorial_mode
KEY_CTRL=shut_up
KEY_FENRIR,KEY_KP4=review_line_begin
#=review_line_end
#=review_line_first_char
#=review_line_last_char
KEY_FENRIR,KEY_ALT,KEY_1=present_first_line
KEY_FENRIR,KEY_ALT,KEY_2=present_last_line
KEY_FENRIR,KEY_SHIFT,KEY_KP4=review_prev_word_phonetic
KEY_FENRIR,KEY_SHIFT,KEY_KP6=review_next_word_phonetic
KEY_FENRIR,KEY_SHIFT,KEY_KP1=review_prev_char_phonetic
KEY_FENRIR,KEY_SHIFT,KEY_KP3=review_next_char_phonetic
KEY_FENRIR,KEY_CTRL,KEY_KP8=review_up
KEY_FENRIR,KEY_CTRL,KEY_KP2=review_down
#=exit_review
KEY_FENRIR,KEY_I=indent_curr_line
KEY_KPPLUS=curr_screen
#=cursor_column
#=cursor_lineno
#=braille_flush
#=braille_return_to_cursor
#=braille_pan_left
#=braille_pan_right
KEY_FENRIR,KEY_CTRL,KEY_1=clear_bookmark_1
KEY_FENRIR,KEY_SHIFT,KEY_1=set_bookmark_1
KEY_FENRIR,KEY_1=bookmark_1
KEY_FENRIR,KEY_CTRL,KEY_2=clear_bookmark_2
KEY_FENRIR,KEY_SHIFT,KEY_2=set_bookmark_2
KEY_FENRIR,KEY_2=bookmark_2
KEY_FENRIR,KEY_CTRL,KEY_3=clear_bookmark_3
KEY_FENRIR,KEY_SHIFT,KEY_3=set_bookmark_3
KEY_FENRIR,KEY_3=bookmark_3
KEY_FENRIR,KEY_CTRL,KEY_4=clear_bookmark_4
KEY_FENRIR,KEY_SHIFT,KEY_4=set_bookmark_4
KEY_FENRIR,KEY_4=bookmark_4
KEY_FENRIR,KEY_CTRL,KEY_5=clear_bookmark_5
KEY_FENRIR,KEY_SHIFT,KEY_5=set_bookmark_5
KEY_FENRIR,KEY_5=bookmark_5
KEY_FENRIR,KEY_CTRL,KEY_6=clear_bookmark_6
KEY_FENRIR,KEY_SHIFT,KEY_6=set_bookmark_6
KEY_FENRIR,KEY_6=bookmark_6
KEY_FENRIR,KEY_CTRL,KEY_7=clear_bookmark_7
KEY_FENRIR,KEY_SHIFT,KEY_7=set_bookmark_7
KEY_FENRIR,KEY_7=bookmark_7
KEY_FENRIR,KEY_CTRL,KEY_8=clear_bookmark_8
KEY_FENRIR,KEY_SHIFT,KEY_8=set_bookmark_8
KEY_FENRIR,KEY_8=bookmark_8
KEY_FENRIR,KEY_CTRL,KEY_9=clear_bookmark_9
KEY_FENRIR,KEY_SHIFT,KEY_9=set_bookmark_9
KEY_FENRIR,KEY_9=bookmark_9
KEY_FENRIR,KEY_CTRL,KEY_0=clear_bookmark_10
KEY_FENRIR,KEY_SHIFT,KEY_0=set_bookmark_10
KEY_FENRIR,KEY_0=bookmark_10
KEY_FENRIR,KEY_KPSLASH=set_window_application
2,KEY_FENRIR,KEY_KPSLASH=clear_window_application
#=last_incoming
KEY_FENRIR,KEY_F2=toggle_braille
KEY_FENRIR,KEY_F3=toggle_sound
KEY_FENRIR,KEY_F9=toggle_punctuation_level
KEY_FENRIR,KEY_RIGHTBRACE=toggle_auto_spell_check
KEY_FENRIR,KEY_BACKSLASH=toggle_output
KEY_FENRIR,KEY_CTRL,KEY_E=toggle_emoticons
key_FENRIR,KEY_KPENTER=toggle_auto_read
KEY_FENRIR,KEY_CTRL,KEY_T=toggle_auto_time
KEY_FENRIR,KEY_KPASTERISK=toggle_highlight_tracking
KEY_FENRIR,KEY_KPMINUS=toggle_barrier
KEY_FENRIR,KEY_Q=quit_fenrir
KEY_FENRIR,KEY_T=time
2,KEY_FENRIR,KEY_T=date
KEY_KPSLASH=toggle_auto_indent
#=toggle_has_attribute
KEY_FENRIR,KEY_S=spell_check
2,KEY_FENRIR,KEY_S=add_word_to_spell_check
KEY_FENRIR,KEY_SHIFT,KEY_S=remove_word_from_spell_check
KEY_FENRIR,KEY_BACKSPACE=forward_keypress
KEY_FENRIR,KEY_ALT,KEY_UP=inc_sound_volume
KEY_FENRIR,KEY_ALT,KEY_DOWN=dec_sound_volume
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_C=clear_clipboard
KEY_FENRIR,KEY_HOME=first_clipboard
KEY_FENRIR,KEY_END=last_clipboard
KEY_FENRIR,KEY_PAGEUP=prev_clipboard
KEY_FENRIR,KEY_PAGEDOWN=next_clipboard
KEY_FENRIR,KEY_SHIFT,KEY_C=curr_clipboard
KEY_FENRIR,KEY_CTRL,KEY_C=copy_last_echo_to_clipboard
KEY_FENRIR,KEY_F5=import_clipboard_from_file
KEY_FENRIR,KEY_F6=export_clipboard_to_file
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_X=remove_marks
KEY_FENRIR,KEY_X=set_mark
KEY_FENRIR,KEY_SHIFT,KEY_X=marked_text
KEY_FENRIR,KEY_F10=toggle_vmenu_mode
KEY_FENRIR,KEY_SPACE=current_quick_menu_entry
KEY_FENRIR,KEY_CTRL,KEY_SPACE=current_quick_menu_value
KEY_FENRIR,KEY_RIGHT=next_quick_menu_entry
KEY_FENRIR,KEY_UP=next_quick_menu_value
KEY_FENRIR,KEY_LEFT=prev_quick_menu_entry
KEY_FENRIR,KEY_DOWN=prev_quick_menu_value
KEY_FENRIR,KEY_CTRL,KEY_S=save_settings
# linux specific
KEY_FENRIR,KEY_F7=import_clipboard_from_x
KEY_FENRIR,KEY_F8=export_clipboard_to_x
KEY_FENRIR,KEY_CTRL,KEY_UP=inc_alsa_volume
KEY_FENRIR,KEY_CTRL,KEY_DOWN=dec_alsa_volume
+30 -25
View File
@@ -46,28 +46,33 @@ _:===:Lienie unten
[customDict] [customDict]
[emoticonDict] [emoticonDict]
# This dictionary uses regexp when prefixed with "regex;", so be sure to escape anything that would be parsed by regexp, e.g. *, ., ^, $, etc. regex;(?:^|\s)(?::-\)|:\))(?:\s|$):===:smile
regex;[\s*|'|"][\s*|'|"]<{-.-}>:===:Raves regex;(?:^|\s)(?::-\(|:\()(?:\s|$):===:frown
regex;[\s*|'|"][\s*|'|"]8-\):===:smile with sunglasses regex;(?:^|\s)(?::-D|:D)(?:\s|$):===:big grin
regex;[\s*|'|"][\s*|'|"]:-/:===:confused regex;(?:^|\s)(?:;-\)|;\))(?:\s|$):===:wink
regex;[\s*|'|"][\s*|'|"]-\.-:===:bugged regex;(?:^|\s)(?::-P|:P)(?:\s|$):===:tongue out
regex;[\s*|'|"][\s*|'|"]>\.<:===:laughing regex;(?:^|\s)(?::-O|:O)(?:\s|$):===:surprised
regex;[\s*|'|"][\s*|'|"]8-X:===:skull regex;(?:^|\s)(?::-/|:/)(?:\s|$):===:skeptical
regex;[\s*|'|"][\s*|'|"]>:\):===:evil smile regex;(?:^|\s)(?:>:-\(|>:\()(?:\s|$):===:angry
regex;[\s*|'|"][\s*|'|"]>:-\):===:evil smile regex;(?:^|\s)(?::'\(|:'-\()(?:\s|$):===:crying
regex;[\s*|'|"][\s*|'|"]\\o/:===:Hurray regex;(?:^|\s)(?::-\||:\|)(?:\s|$):===:neutral
regex;[\s*|'|"][\s*|'|"]:/:===:confused regex;(?:^|\s)(?:8-\)|B-\))(?:\s|$):===:cool
regex;[\s*|'|"][\s*|'|"]8\):===:smile with sunglasses regex;(?:^|\s)8-X(?:\s|$):===:skull and crossbones
regex;[\s*|'|"]:D[\s*|'|"]:===:laugh regex;(?:^|\s)(?:X-X|x-x)(?:\s|$):===:knocked out
regex;[\s*|'|"];\)[\s*|'|"]:===:wink regex;(?:^|\s)(?::-X|:-x|:X|:x)(?:\s|$):===:lips sealed
regex;[\s*|'|"]XD[\s*|'|"]:===:LOL regex;(?:^|\s)XD(?:\s|$):===:laughing hard
regex;[\s*|'|"]:-\)[\s*|'|"]:===:smile regex;(?:^|\s)\^_\^(?:\s|$):===:happy
regex;[\s*|'|"]:\)[\s*|'|"]:===:smile regex;(?:^|\s)-_-(?:\s|$):===:annoyed
regex;[\s*|'|"]->[\s*|'|"]:===:arrow right regex;(?:^|\s)o_O(?:\s|$):===:confused
# example for arrow left regex;(?:^|\s)>_<(?:\s|$):===:frustrated
#(?:[ |^])(<-)(?:[ ,.!?$]):===:arrow left regex;(?:^|\s)\\o/(?:\s|$):===:hurray
# or regex;(?:^|\s)<3(?:\s|$):===:heart
#([ |^])<-([ ,.!?$]):===:arrow left\2 regex;(?:^|\s)</3(?:\s|$):===:broken heart
regex;[\s*|'|"]<-[\s*|'|"]:===:arrow left regex;(?:^|\s)(?:Oo|oO)(?:\s|$):===:WTF?
regex;[\s*|'|"][O|o][O|o][\s*|'|"]:===:WTF? regex;(?:^|\s)<{-.-}>(?:\s|$):===:Raves
regex;[\s*|'|"]\^\^[\s*|'|"]:===:enjoy smile regex;(?:^|\s)-\.-(?:\s|$):===:bugged
regex;(?:^|\s)>\.<(?:\s|$):===:laughing
regex;(?:^|\s)(?:>:\)|>:-\))(?:\s|$):===:evil smile
regex;(?:^|\s)\^\^(?:\s|$):===:enjoy smile
regex;(?:^|\s)->(?:\s|$):===:arrow right
regex;(?:^|\s)<-(?:\s|$):===:arrow left
+31 -25
View File
@@ -5,7 +5,7 @@
[levelDict] [levelDict]
none:===: none:===:
some:===:-$~+*-/\@# some:===:-$~+*-/\@#
most:===:.,:-$~+*-/\@!#%^&*()[]}{<>; most:===:.,:-_$~+*-/\@!#%^&*()[]}{<>;
all:===:!"#$%& \'()*+,-./:;<=>?@[\\]^_`{|}~ all:===:!"#$%& \'()*+,-./:;<=>?@[\\]^_`{|}~
[punctDict] [punctDict]
@@ -46,27 +46,33 @@ _:===:line
[customDict] [customDict]
[emoticonDict] [emoticonDict]
# This dictionary uses regexp when prefixed with "regex;", so be sure to escape anything that would be parsed by regexp, e.g. *, ., ^, $, etc. regex;(?:^|\s)(?::-\)|:\))(?:\s|$):===:smile
regex;[\s*|'|"][\s*|'|"]<{-.-}>:===:Raves regex;(?:^|\s)(?::-\(|:\()(?:\s|$):===:frown
regex;[\s*|'|"][\s*|'|"]8-\):===:smile with sunglasses regex;(?:^|\s)(?::-D|:D)(?:\s|$):===:big grin
regex;[\s*|'|"][\s*|'|"]:-/:===:confused regex;(?:^|\s)(?:;-\)|;\))(?:\s|$):===:wink
regex;[\s*|'|"][\s*|'|"]-\.-:===:bugged regex;(?:^|\s)(?::-P|:P)(?:\s|$):===:tongue out
regex;[\s*|'|"][\s*|'|"]>\.<:===:laughing regex;(?:^|\s)(?::-O|:O)(?:\s|$):===:surprised
regex;[\s*|'|"][\s*|'|"]8-X:===:skull regex;(?:^|\s)(?::-/|:/)(?:\s|$):===:skeptical
regex;[\s*|'|"][\s*|'|"]>:\):===:evil smile regex;(?:^|\s)(?:>:-\(|>:\()(?:\s|$):===:angry
regex;[\s*|'|"][\s*|'|"]>:-\):===:evil smile regex;(?:^|\s)(?::'\(|:'-\()(?:\s|$):===:crying
regex;[\s*|'|"][\s*|'|"]\\o/:===:Hurray regex;(?:^|\s)(?::-\||:\|)(?:\s|$):===:neutral
regex;[\s*|'|"][\s*|'|"]:/:===:confused regex;(?:^|\s)(?:8-\)|B-\))(?:\s|$):===:cool
regex;[\s*|'|"]:D[\s*|'|"]:===:laugh regex;(?:^|\s)8-X(?:\s|$):===:skull and crossbones
regex;[\s*|'|"];\)[\s*|'|"]:===:wink regex;(?:^|\s)(?:X-X|x-x)(?:\s|$):===:knocked out
regex;[\s*|'|"]XD[\s*|'|"]:===:LOL regex;(?:^|\s)(?::-X|:-x|:X|:x)(?:\s|$):===:lips sealed
regex;[\s*|'|"]:-\)[\s*|'|"]:===:smile regex;(?:^|\s)XD(?:\s|$):===:laughing hard
regex;[\s*|'|"]:\)[\s*|'|"]:===:smile regex;(?:^|\s)\^_\^(?:\s|$):===:happy
regex;[\s*|'|"]->[\s*|'|"]:===:arrow right regex;(?:^|\s)-_-(?:\s|$):===:annoyed
# example for arrow left regex;(?:^|\s)o_O(?:\s|$):===:confused
#(?:[ |^])(<-)(?:[ ,.!?$]):===:arrow left regex;(?:^|\s)>_<(?:\s|$):===:frustrated
# or regex;(?:^|\s)\\o/(?:\s|$):===:hurray
#([ |^])<-([ ,.!?$]):===:arrow left\2 regex;(?:^|\s)<3(?:\s|$):===:heart
regex;[\s*|'|"]<-[\s*|'|"]:===:arrow left regex;(?:^|\s)</3(?:\s|$):===:broken heart
regex;[\s+|'|"](Oo)|(oO)[\s+|'|"]:===:WTF? regex;(?:^|\s)(?:Oo|oO)(?:\s|$):===:WTF?
regex;[\s*|'|"]\^\^[\s*|'|"]:===:enjoy smile regex;(?:^|\s)<{-.-}>(?:\s|$):===:Raves
regex;(?:^|\s)-\.-(?:\s|$):===:bugged
regex;(?:^|\s)>\.<(?:\s|$):===:laughing
regex;(?:^|\s)(?:>:\)|>:-\))(?:\s|$):===:evil smile
regex;(?:^|\s)\^\^(?:\s|$):===:enjoy smile
regex;(?:^|\s)->(?:\s|$):===:arrow right
regex;(?:^|\s)<-(?:\s|$):===:arrow left
+31 -25
View File
@@ -5,7 +5,7 @@
[levelDict] [levelDict]
none:===: none:===:
some:===:-$~+*-/\@ some:===:-$~+*-/\@
most:===:.,:-$~+*-/\@!#%^&*()[]}{<>; most:===:.,:-$~+*-_/\@!#%^&*()[]}{<>;
all:===:!"#$%& \'()*+,-./:;<=>?@[\\]^_`{|}~ all:===:!"#$%& \'()*+,-./:;<=>?@[\\]^_`{|}~
[punctDict] [punctDict]
@@ -46,27 +46,33 @@ _:===:line
[customDict] [customDict]
[emoticonDict] [emoticonDict]
# This dictionary uses regexp when prefixed with "regex;", so be sure to escape anything that would be parsed by regexp, e.g. *, ., ^, $, etc. regex;(?:^|\s)(?::-\)|:\))(?:\s|$):===:smile
regex;[\s*|'|"][\s*|'|"]<{-.-}>:===:Raves regex;(?:^|\s)(?::-\(|:\()(?:\s|$):===:frown
regex;[\s*|'|"][\s*|'|"]8-\):===:smile with sunglasses regex;(?:^|\s)(?::-D|:D)(?:\s|$):===:big grin
regex;[\s*|'|"][\s*|'|"]:-/:===:confused regex;(?:^|\s)(?:;-\)|;\))(?:\s|$):===:wink
regex;[\s*|'|"][\s*|'|"]-\.-:===:bugged regex;(?:^|\s)(?::-P|:P)(?:\s|$):===:tongue out
regex;[\s*|'|"][\s*|'|"]>\.<:===:laughing regex;(?:^|\s)(?::-O|:O)(?:\s|$):===:surprised
regex;[\s*|'|"][\s*|'|"]8-X:===:skull regex;(?:^|\s)(?::-/|:/)(?:\s|$):===:skeptical
regex;[\s*|'|"][\s*|'|"]>:\):===:evil smile regex;(?:^|\s)(?:>:-\(|>:\()(?:\s|$):===:angry
regex;[\s*|'|"][\s*|'|"]>:-\):===:evil smile regex;(?:^|\s)(?::'\(|:'-\()(?:\s|$):===:crying
regex;[\s*|'|"][\s*|'|"]\\o/:===:Hurray regex;(?:^|\s)(?::-\||:\|)(?:\s|$):===:neutral
regex;[\s*|'|"][\s*|'|"]:/:===:confused regex;(?:^|\s)(?:8-\)|B-\))(?:\s|$):===:cool
regex;[\s*|'|"]:D[\s*|'|"]:===:laugh regex;(?:^|\s)8-X(?:\s|$):===:skull and crossbones
regex;[\s*|'|"];\)[\s*|'|"]:===:wink regex;(?:^|\s)(?:X-X|x-x)(?:\s|$):===:knocked out
regex;[\s*|'|"]XD[\s*|'|"]:===:LOL regex;(?:^|\s)(?::-X|:-x|:X|:x)(?:\s|$):===:lips sealed
regex;[\s*|'|"]:-\)[\s*|'|"]:===:smile regex;(?:^|\s)XD(?:\s|$):===:laughing hard
regex;[\s*|'|"]:\)[\s*|'|"]:===:smile regex;(?:^|\s)\^_\^(?:\s|$):===:happy
regex;[\s*|'|"]->[\s*|'|"]:===:arrow right regex;(?:^|\s)-_-(?:\s|$):===:annoyed
# example for arrow left regex;(?:^|\s)o_O(?:\s|$):===:confused
#(?:[ |^])(<-)(?:[ ,.!?$]):===:arrow left regex;(?:^|\s)>_<(?:\s|$):===:frustrated
# or regex;(?:^|\s)\\o/(?:\s|$):===:hurray
#([ |^])<-([ ,.!?$]):===:arrow left\2 regex;(?:^|\s)<3(?:\s|$):===:heart
regex;[\s*|'|"]<-[\s*|'|"]:===:arrow left regex;(?:^|\s)</3(?:\s|$):===:broken heart
regex;[\s+|'|"][O|o][O|o][\s+|'|"]:===:WTF? regex;(?:^|\s)(?:Oo|oO)(?:\s|$):===:WTF?
regex;[\s*|'|"]\^\^[\s*|'|"]:===:enjoy smile regex;(?:^|\s)<{-.-}>(?:\s|$):===:Raves
regex;(?:^|\s)-\.-(?:\s|$):===:bugged
regex;(?:^|\s)>\.<(?:\s|$):===:laughing
regex;(?:^|\s)(?:>:\)|>:-\))(?:\s|$):===:evil smile
regex;(?:^|\s)\^\^(?:\s|$):===:enjoy smile
regex;(?:^|\s)->(?:\s|$):===:arrow right
regex;(?:^|\s)<-(?:\s|$):===:arrow left
+30 -25
View File
@@ -46,28 +46,33 @@ _:===:subrayado
[customDict] [customDict]
[emoticonDict] [emoticonDict]
# This dictionary uses regexp when prefixed with "regex;", so be sure to escape anything that would be parsed by regexp, e.g. *, ., ^, $, etc. regex;(?:^|\s)(?::-\)|:\))(?:\s|$):===:smile
regex;[\s*|'|"][\s*|'|"]<{-.-}>:===:Raves regex;(?:^|\s)(?::-\(|:\()(?:\s|$):===:frown
regex;[\s*|'|"][\s*|'|"]8-\):===:smile with sunglasses regex;(?:^|\s)(?::-D|:D)(?:\s|$):===:big grin
regex;[\s*|'|"][\s*|'|"]:-/:===:confused regex;(?:^|\s)(?:;-\)|;\))(?:\s|$):===:wink
regex;[\s*|'|"][\s*|'|"]-\.-:===:bugged regex;(?:^|\s)(?::-P|:P)(?:\s|$):===:tongue out
regex;[\s*|'|"][\s*|'|"]>\.<:===:laughing regex;(?:^|\s)(?::-O|:O)(?:\s|$):===:surprised
regex;[\s*|'|"][\s*|'|"]8-X:===:skull regex;(?:^|\s)(?::-/|:/)(?:\s|$):===:skeptical
regex;[\s*|'|"][\s*|'|"]>:\):===:evil smile regex;(?:^|\s)(?:>:-\(|>:\()(?:\s|$):===:angry
regex;[\s*|'|"][\s*|'|"]>:-\):===:evil smile regex;(?:^|\s)(?::'\(|:'-\()(?:\s|$):===:crying
regex;[\s*|'|"][\s*|'|"]\\o/:===:Hurray regex;(?:^|\s)(?::-\||:\|)(?:\s|$):===:neutral
regex;[\s*|'|"][\s*|'|"]:/:===:confused regex;(?:^|\s)(?:8-\)|B-\))(?:\s|$):===:cool
regex;[\s*|'|"][\s*|'|"]8\):===:smile with sunglasses regex;(?:^|\s)8-X(?:\s|$):===:skull and crossbones
regex;[\s*|'|"]:D[\s*|'|"]:===:laugh regex;(?:^|\s)(?:X-X|x-x)(?:\s|$):===:knocked out
regex;[\s*|'|"];\)[\s*|'|"]:===:wink regex;(?:^|\s)(?::-X|:-x|:X|:x)(?:\s|$):===:lips sealed
regex;[\s*|'|"]XD[\s*|'|"]:===:LOL regex;(?:^|\s)XD(?:\s|$):===:laughing hard
regex;[\s*|'|"]:-\)[\s*|'|"]:===:smile regex;(?:^|\s)\^_\^(?:\s|$):===:happy
regex;[\s*|'|"]:\)[\s*|'|"]:===:smile regex;(?:^|\s)-_-(?:\s|$):===:annoyed
regex;[\s*|'|"]->[\s*|'|"]:===:arrow right regex;(?:^|\s)o_O(?:\s|$):===:confused
# example for arrow left regex;(?:^|\s)>_<(?:\s|$):===:frustrated
#(?:[ |^])(<-)(?:[ ,.!?$]):===:arrow left regex;(?:^|\s)\\o/(?:\s|$):===:hurray
# or regex;(?:^|\s)<3(?:\s|$):===:heart
#([ |^])<-([ ,.!?$]):===:arrow left\2 regex;(?:^|\s)</3(?:\s|$):===:broken heart
regex;[\s*|'|"]<-[\s*|'|"]:===:arrow left regex;(?:^|\s)(?:Oo|oO)(?:\s|$):===:WTF?
regex;[\s*|'|"][O|o][O|o][\s*|'|"]:===:WTF? regex;(?:^|\s)<{-.-}>(?:\s|$):===:Raves
regex;[\s*|'|"]\^\^[\s*|'|"]:===:enjoy smile regex;(?:^|\s)-\.-(?:\s|$):===:bugged
regex;(?:^|\s)>\.<(?:\s|$):===:laughing
regex;(?:^|\s)(?:>:\)|>:-\))(?:\s|$):===:evil smile
regex;(?:^|\s)\^\^(?:\s|$):===:enjoy smile
regex;(?:^|\s)->(?:\s|$):===:arrow right
regex;(?:^|\s)<-(?:\s|$):===:arrow left
+30 -25
View File
@@ -46,28 +46,33 @@ _:===:souligné
[customDict] [customDict]
[emoticonDict] [emoticonDict]
# This dictionary uses regexp when prefixed with "regex;", so be sure to escape anything that would be parsed by regexp, e.g. *, ., ^, $, etc. regex;(?:^|\s)(?::-\)|:\))(?:\s|$):===:smile
regex;[\s*|'|"][\s*|'|"]<{-.-}>:===:Raves regex;(?:^|\s)(?::-\(|:\()(?:\s|$):===:frown
regex;[\s*|'|"][\s*|'|"]8-\):===:smile with sunglasses regex;(?:^|\s)(?::-D|:D)(?:\s|$):===:big grin
regex;[\s*|'|"][\s*|'|"]:-/:===:confused regex;(?:^|\s)(?:;-\)|;\))(?:\s|$):===:wink
regex;[\s*|'|"][\s*|'|"]-\.-:===:bugged regex;(?:^|\s)(?::-P|:P)(?:\s|$):===:tongue out
regex;[\s*|'|"][\s*|'|"]>\.<:===:laughing regex;(?:^|\s)(?::-O|:O)(?:\s|$):===:surprised
regex;[\s*|'|"][\s*|'|"]8-X:===:skull regex;(?:^|\s)(?::-/|:/)(?:\s|$):===:skeptical
regex;[\s*|'|"][\s*|'|"]>:\):===:evil smile regex;(?:^|\s)(?:>:-\(|>:\()(?:\s|$):===:angry
regex;[\s*|'|"][\s*|'|"]>:-\):===:evil smile regex;(?:^|\s)(?::'\(|:'-\()(?:\s|$):===:crying
regex;[\s*|'|"][\s*|'|"]\\o/:===:Hurray regex;(?:^|\s)(?::-\||:\|)(?:\s|$):===:neutral
regex;[\s*|'|"][\s*|'|"]:/:===:confused regex;(?:^|\s)(?:8-\)|B-\))(?:\s|$):===:cool
regex;[\s*|'|"][\s*|'|"]8\):===:smile with sunglasses regex;(?:^|\s)8-X(?:\s|$):===:skull and crossbones
regex;[\s*|'|"]:D[\s*|'|"]:===:laugh regex;(?:^|\s)(?:X-X|x-x)(?:\s|$):===:knocked out
regex;[\s*|'|"];\)[\s*|'|"]:===:wink regex;(?:^|\s)(?::-X|:-x|:X|:x)(?:\s|$):===:lips sealed
regex;[\s*|'|"]XD[\s*|'|"]:===:LOL regex;(?:^|\s)XD(?:\s|$):===:laughing hard
regex;[\s*|'|"]:-\)[\s*|'|"]:===:smile regex;(?:^|\s)\^_\^(?:\s|$):===:happy
regex;[\s*|'|"]:\)[\s*|'|"]:===:smile regex;(?:^|\s)-_-(?:\s|$):===:annoyed
regex;[\s*|'|"]->[\s*|'|"]:===:arrow right regex;(?:^|\s)o_O(?:\s|$):===:confused
# example for arrow left regex;(?:^|\s)>_<(?:\s|$):===:frustrated
#(?:[ |^])(<-)(?:[ ,.!?$]):===:arrow left regex;(?:^|\s)\\o/(?:\s|$):===:hurray
# or regex;(?:^|\s)<3(?:\s|$):===:heart
#([ |^])<-([ ,.!?$]):===:arrow left\2 regex;(?:^|\s)</3(?:\s|$):===:broken heart
regex;[\s*|'|"]<-[\s*|'|"]:===:arrow left regex;(?:^|\s)(?:Oo|oO)(?:\s|$):===:WTF?
regex;[\s*|'|"][O|o][O|o][\s*|'|"]:===:WTF? regex;(?:^|\s)<{-.-}>(?:\s|$):===:Raves
regex;[\s*|'|"]\^\^[\s*|'|"]:===:enjoy smile regex;(?:^|\s)-\.-(?:\s|$):===:bugged
regex;(?:^|\s)>\.<(?:\s|$):===:laughing
regex;(?:^|\s)(?:>:\)|>:-\))(?:\s|$):===:evil smile
regex;(?:^|\s)\^\^(?:\s|$):===:enjoy smile
regex;(?:^|\s)->(?:\s|$):===:arrow right
regex;(?:^|\s)<-(?:\s|$):===:arrow left
+30 -25
View File
@@ -46,28 +46,33 @@ _:===:podkreślnik
[customDict] [customDict]
[emoticonDict] [emoticonDict]
# This dictionary uses regexp when prefixed with "regex;", so be sure to escape anything that would be parsed by regexp, e.g. *, ., ^, $, etc. regex;(?:^|\s)(?::-\)|:\))(?:\s|$):===:smile
regex;[\s*|'|"][\s*|'|"]<{-.-}>:===:Raves regex;(?:^|\s)(?::-\(|:\()(?:\s|$):===:frown
regex;[\s*|'|"][\s*|'|"]8-\):===:smile with sunglasses regex;(?:^|\s)(?::-D|:D)(?:\s|$):===:big grin
regex;[\s*|'|"][\s*|'|"]:-/:===:confused regex;(?:^|\s)(?:;-\)|;\))(?:\s|$):===:wink
regex;[\s*|'|"][\s*|'|"]-\.-:===:bugged regex;(?:^|\s)(?::-P|:P)(?:\s|$):===:tongue out
regex;[\s*|'|"][\s*|'|"]>\.<:===:laughing regex;(?:^|\s)(?::-O|:O)(?:\s|$):===:surprised
regex;[\s*|'|"][\s*|'|"]8-X:===:skull regex;(?:^|\s)(?::-/|:/)(?:\s|$):===:skeptical
regex;[\s*|'|"][\s*|'|"]>:\):===:evil smile regex;(?:^|\s)(?:>:-\(|>:\()(?:\s|$):===:angry
regex;[\s*|'|"][\s*|'|"]>:-\):===:evil smile regex;(?:^|\s)(?::'\(|:'-\()(?:\s|$):===:crying
regex;[\s*|'|"][\s*|'|"]\\o/:===:Hurray regex;(?:^|\s)(?::-\||:\|)(?:\s|$):===:neutral
regex;[\s*|'|"][\s*|'|"]:/:===:confused regex;(?:^|\s)(?:8-\)|B-\))(?:\s|$):===:cool
regex;[\s*|'|"][\s*|'|"]8\):===:smile with sunglasses regex;(?:^|\s)8-X(?:\s|$):===:skull and crossbones
regex;[\s*|'|"]:D[\s*|'|"]:===:laugh regex;(?:^|\s)(?:X-X|x-x)(?:\s|$):===:knocked out
regex;[\s*|'|"];\)[\s*|'|"]:===:wink regex;(?:^|\s)(?::-X|:-x|:X|:x)(?:\s|$):===:lips sealed
regex;[\s*|'|"]XD[\s*|'|"]:===:LOL regex;(?:^|\s)XD(?:\s|$):===:laughing hard
regex;[\s*|'|"]:-\)[\s*|'|"]:===:smile regex;(?:^|\s)\^_\^(?:\s|$):===:happy
regex;[\s*|'|"]:\)[\s*|'|"]:===:smile regex;(?:^|\s)-_-(?:\s|$):===:annoyed
regex;[\s*|'|"]->[\s*|'|"]:===:arrow right regex;(?:^|\s)o_O(?:\s|$):===:confused
# example for arrow left regex;(?:^|\s)>_<(?:\s|$):===:frustrated
#(?:[ |^])(<-)(?:[ ,.!?$]):===:arrow left regex;(?:^|\s)\\o/(?:\s|$):===:hurray
# or regex;(?:^|\s)<3(?:\s|$):===:heart
#([ |^])<-([ ,.!?$]):===:arrow left\2 regex;(?:^|\s)</3(?:\s|$):===:broken heart
regex;[\s*|'|"]<-[\s*|'|"]:===:arrow left regex;(?:^|\s)(?:Oo|oO)(?:\s|$):===:WTF?
regex;[\s*|'|"][O|o][O|o][\s*|'|"]:===:WTF? regex;(?:^|\s)<{-.-}>(?:\s|$):===:Raves
regex;[\s*|'|"]\^\^[\s*|'|"]:===:enjoy smile regex;(?:^|\s)-\.-(?:\s|$):===:bugged
regex;(?:^|\s)>\.<(?:\s|$):===:laughing
regex;(?:^|\s)(?:>:\)|>:-\))(?:\s|$):===:evil smile
regex;(?:^|\s)\^\^(?:\s|$):===:enjoy smile
regex;(?:^|\s)->(?:\s|$):===:arrow right
regex;(?:^|\s)<-(?:\s|$):===:arrow left
+31 -25
View File
@@ -4,7 +4,7 @@
# the entrys are seperated with :===: in words colon tripple equal colon ( to not collide with substitutions) # the entrys are seperated with :===: in words colon tripple equal colon ( to not collide with substitutions)
[levelDict] [levelDict]
none:===: none:===:
some:===:-$~+*-/\@ some:===:-$~+*-/\@_
most:===:.,:-$~+*-/\@!#%^&*()[]}{<>; most:===:.,:-$~+*-/\@!#%^&*()[]}{<>;
all:===:!"#$%& \'()*+,-./:;<=>?@[\\]^_`{|}~ all:===:!"#$%& \'()*+,-./:;<=>?@[\\]^_`{|}~
@@ -52,27 +52,33 @@ regex;(?i)shit:===:shitt
regex;(?i)slither:===:slitther regex;(?i)slither:===:slitther
[emoticonDict] [emoticonDict]
# This dictionary uses regexp when prefixed with "regex;", so be sure to escape anything that would be parsed by regexp, e.g. *, ., ^, $, etc. regex;(?:^|\s)(?::-\)|:\))(?:\s|$):===:smile
regex;[\s*|'|"][\s*|'|"]<{-.-}>:===:Raves regex;(?:^|\s)(?::-\(|:\()(?:\s|$):===:frown
regex;[\s*|'|"][\s*|'|"]8-\):===:smile with sunglasses regex;(?:^|\s)(?::-D|:D)(?:\s|$):===:big grin
regex;[\s*|'|"][\s*|'|"]:-/:===:confused regex;(?:^|\s)(?:;-\)|;\))(?:\s|$):===:wink
regex;[\s*|'|"][\s*|'|"]-\.-:===:bugged regex;(?:^|\s)(?::-P|:P)(?:\s|$):===:tongue out
regex;[\s*|'|"][\s*|'|"]>\.<:===:laughing regex;(?:^|\s)(?::-O|:O)(?:\s|$):===:surprised
regex;[\s*|'|"][\s*|'|"]8-X:===:skull regex;(?:^|\s)(?::-/|:/)(?:\s|$):===:skeptical
regex;[\s*|'|"][\s*|'|"]>:\):===:evil smile regex;(?:^|\s)(?:>:-\(|>:\()(?:\s|$):===:angry
regex;[\s*|'|"][\s*|'|"]>:-\):===:evil smile regex;(?:^|\s)(?::'\(|:'-\()(?:\s|$):===:crying
regex;[\s*|'|"][\s*|'|"]\\o/:===:Hurray regex;(?:^|\s)(?::-\||:\|)(?:\s|$):===:neutral
regex;[\s*|'|"][\s*|'|"]:/:===:confused regex;(?:^|\s)(?:8-\)|B-\))(?:\s|$):===:cool
regex;[\s*|'|"]:D[\s*|'|"]:===:laugh regex;(?:^|\s)8-X(?:\s|$):===:skull and crossbones
regex;[\s*|'|"];\)[\s*|'|"]:===:wink regex;(?:^|\s)(?:X-X|x-x)(?:\s|$):===:knocked out
regex;[\s*|'|"]XD[\s*|'|"]:===:LOL regex;(?:^|\s)(?::-X|:-x|:X|:x)(?:\s|$):===:lips sealed
regex;[\s*|'|"]:-\)[\s*|'|"]:===:smile regex;(?:^|\s)XD(?:\s|$):===:laughing hard
regex;[\s*|'|"]:\)[\s*|'|"]:===:smile regex;(?:^|\s)\^_\^(?:\s|$):===:happy
regex;[\s*|'|"]->[\s*|'|"]:===:arrow right regex;(?:^|\s)-_-(?:\s|$):===:annoyed
# example for arrow left regex;(?:^|\s)o_O(?:\s|$):===:confused
#(?:[ |^])(<-)(?:[ ,.!?$]):===:arrow left regex;(?:^|\s)>_<(?:\s|$):===:frustrated
# or regex;(?:^|\s)\\o/(?:\s|$):===:hurray
#([ |^])<-([ ,.!?$]):===:arrow left\2 regex;(?:^|\s)<3(?:\s|$):===:heart
regex;[\s*|'|"]<-[\s*|'|"]:===:arrow left regex;(?:^|\s)</3(?:\s|$):===:broken heart
regex;[\s+|'|"][O|o][O|o][\s+|'|"]:===:WTF? regex;(?:^|\s)(?:Oo|oO)(?:\s|$):===:WTF?
regex;[\s*|'|"]\^\^[\s*|'|"]:===:enjoy smile regex;(?:^|\s)<{-.-}>(?:\s|$):===:Raves
regex;(?:^|\s)-\.-(?:\s|$):===:bugged
regex;(?:^|\s)>\.<(?:\s|$):===:laughing
regex;(?:^|\s)(?:>:\)|>:-\))(?:\s|$):===:evil smile
regex;(?:^|\s)\^\^(?:\s|$):===:enjoy smile
regex;(?:^|\s)->(?:\s|$):===:arrow right
regex;(?:^|\s)<-(?:\s|$):===:arrow left
+5 -5
View File
@@ -15,7 +15,7 @@ theme=default
# Sound volume controls how loud the sounds for your selected soundpack are. # Sound volume controls how loud the sounds for your selected soundpack are.
# 0 is quietest, 1.0 is loudest. # 0 is quietest, 1.0 is loudest.
volume=1.0 volume=0.7
# shell commands for generic sound driver # shell commands for generic sound driver
# the folowing variable are substituted # the folowing variable are substituted
@@ -92,8 +92,8 @@ fenrirMaxRate=450
driver=vcsaDriver driver=vcsaDriver
encoding=auto encoding=auto
screenUpdateDelay=0.05 screenUpdateDelay=0.05
suspendingScreen= ignoreScreen=
autodetectSuspendingScreen=True autodetectIgnoreScreen=True
[keyboard] [keyboard]
driver=evdevDriver driver=evdevDriver
@@ -131,7 +131,7 @@ punctuationProfile=default
punctuationLevel=some punctuationLevel=some
respectPunctuationPause=True respectPunctuationPause=True
newLinePause=True newLinePause=True
numberOfClipboards=10 numberOfClipboards=50
# used path for "export_clipboard_to_file" # used path for "export_clipboard_to_file"
# $user is replaced by username # $user is replaced by username
#clipboardExportPath=/home/$user/fenrirClipboard #clipboardExportPath=/home/$user/fenrirClipboard
@@ -190,7 +190,7 @@ enableSettingsRemote=True
enableCommandRemote=True enableCommandRemote=True
[barrier] [barrier]
enabled=True enabled=False
leftBarriers=│└┌─ leftBarriers=│└┌─
rightBarriers=│┘┐─ rightBarriers=│┘┐─
+5 -5
View File
@@ -1,12 +1,11 @@
#!/bin/bash #!/usr/bin/env bash
#Basic install script for Fenrir. #Basic install script for Fenrir.
read -p "This will install Fenrir. Press ctrl+C to cancel, or enter to continue." continue read -rp "This will install Fenrir. Press ctrl+C to cancel, or enter to continue."
# Fenrir main application # Fenrir main application
install -m755 -d /opt/fenrirscreenreader install -m755 -d /opt/fenrirscreenreader
cp -af src/* /opt/fenrirscreenreader cp -af src/* /opt/fenrirscreenreader
ln -fs /opt/fenrirscreenreader/fenrir-daemon /usr/bin/fenrir-daemon
ln -fs /opt/fenrirscreenreader/fenrir /usr/bin/fenrir ln -fs /opt/fenrirscreenreader/fenrir /usr/bin/fenrir
# tools # tools
install -m755 -d /usr/share/fenrirscreenreader/tools install -m755 -d /usr/share/fenrirscreenreader/tools
@@ -33,8 +32,9 @@ cp -af config/sound/template /usr/share/sounds/fenrirscreenreader/template
# config # config
if [ -f "/etc/fenrirscreenreader/settings/settings.conf" ]; then if [ -f "/etc/fenrirscreenreader/settings/settings.conf" ]; then
echo "Do you want to overwrite your current global settings? (y/n)" echo "Do you want to overwrite your current global settings? (y/n)"
read yn read -r yn
if [ $yn = "Y" -o $yn = "y" ]; then yn="${yn:0:1}"
if [[ "${yn^}" == "Y" ]]; then
mv /etc/fenrirscreenreader/settings/settings.conf /etc/fenrirscreenreader/settings/settings.conf.bak mv /etc/fenrirscreenreader/settings/settings.conf /etc/fenrirscreenreader/settings/settings.conf.bak
echo "Your old settings.conf has been backed up to settings.conf.bak." echo "Your old settings.conf has been backed up to settings.conf.bak."
install -m644 -D "config/settings/settings.conf" /etc/fenrirscreenreader/settings/settings.conf install -m644 -D "config/settings/settings.conf" /etc/fenrirscreenreader/settings/settings.conf
+8 -6
View File
@@ -1,7 +1,9 @@
evdev>=1.1.2 daemonize
daemonize>=2.5.0 evdev
dbus-python>=1.2.8
pyudev>=0.21.0
pexpect pexpect
pyttsx3 pyenchant
pyte>=0.7.0 pyperclip
pyte
pyudev
pyxdg
setproctitle
+5 -4
View File
@@ -99,10 +99,11 @@ setup(
"evdev>=1.1.2", "evdev>=1.1.2",
"daemonize>=2.5.0", "daemonize>=2.5.0",
"dbus-python>=1.2.8", "dbus-python>=1.2.8",
"pyperclip",
"pyudev>=0.21.0", "pyudev>=0.21.0",
"setuptools", "setuptools",
"setproctitle",
"pexpect", "pexpect",
"pyttsx3",
"pyte>=0.7.0", "pyte>=0.7.0",
], ],
) )
@@ -112,10 +113,10 @@ if not forceSettingsFlag:
# create settings file from example if not exist # create settings file from example if not exist
if not os.path.isfile('/etc/fenrirscreenreader/settings/settings.conf'): if not os.path.isfile('/etc/fenrirscreenreader/settings/settings.conf'):
try: try:
copyfile('/etc/fenrirscreenreader/settings/settings.conf.example', '/etc/fenrirscreenreader/settings/settings.conf') copyfile('config/fenrirscreenreader/settings/settings.conf', '/etc/fenrirscreenreader/settings/settings.conf')
print('create settings file in /etc/fenrirscreenreader/settings/settings.conf') print('create settings file in /etc/fenrirscreenreader/settings/settings.conf')
except: except OSError as e:
pass print(f"Could not copy settings file to destination: {e}")
else: else:
print('settings.conf file found. It is not overwritten automatical') print('settings.conf file found. It is not overwritten automatical')
@@ -0,0 +1,90 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import os
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('cycles between available keyboard layouts')
def getAvailableLayouts(self):
"""Get list of available keyboard layout files"""
layouts = []
# Check standard locations for keyboard layouts
settingsRoot = '/etc/fenrirscreenreader/'
if not os.path.exists(settingsRoot):
# Fallback to source directory
import fenrirscreenreader
fenrirPath = os.path.dirname(fenrirscreenreader.__file__)
settingsRoot = fenrirPath + '/../../config/'
keyboardPath = settingsRoot + 'keyboard/'
if os.path.exists(keyboardPath):
for file in os.listdir(keyboardPath):
if file.endswith('.conf') and not file.startswith('__') and not file.lower().startswith('pty'):
layout_name = file.replace('.conf', '')
if layout_name not in layouts:
layouts.append(layout_name)
# Ensure we have at least basic layouts
if not layouts:
layouts = ['desktop', 'laptop']
else:
layouts.sort()
return layouts
def run(self):
current_layout = self.env['runtime']['settingsManager'].getSetting('keyboard', 'keyboardLayout')
# Extract layout name from full path if needed
if '/' in current_layout:
current_layout = os.path.basename(current_layout).replace('.conf', '')
# Get available layouts
available_layouts = self.getAvailableLayouts()
# Find next layout in cycle
try:
current_index = available_layouts.index(current_layout)
next_index = (current_index + 1) % len(available_layouts)
except ValueError:
# If current layout not found, start from beginning
next_index = 0
next_layout = available_layouts[next_index]
# Update setting and reload shortcuts
self.env['runtime']['settingsManager'].setSetting('keyboard', 'keyboardLayout', next_layout)
# Reload shortcuts with new layout
try:
self.env['runtime']['inputManager'].reloadShortcuts()
self.env['runtime']['outputManager'].presentText(
_('Switched to {} keyboard layout').format(next_layout),
interrupt=True
)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut(
"Error reloading shortcuts: " + str(e),
debug.debugLevel.ERROR
)
self.env['runtime']['outputManager'].presentText(
_('Error switching keyboard layout'),
interrupt=True
)
def setCallback(self, callback):
pass
@@ -5,15 +5,17 @@
# By Chrys, Storm Dragon, and contributers. # By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug from fenrirscreenreader.core import debug
import subprocess, os import os
from subprocess import Popen, PIPE import importlib
import _thread import _thread
import pyperclip
class command(): class command():
def __init__(self): def __init__(self):
pass pass
def initialize(self, environment): def initialize(self, environment, scriptPath=''):
self.env = environment self.env = environment
self.scriptPath = scriptPath
def shutdown(self): def shutdown(self):
pass pass
def getDescription(self): def getDescription(self):
@@ -22,56 +24,48 @@ class command():
_thread.start_new_thread(self._threadRun , ()) _thread.start_new_thread(self._threadRun , ())
def _threadRun(self): def _threadRun(self):
try: try:
# Check if clipboard is empty
if self.env['runtime']['memoryManager'].isIndexListEmpty('clipboardHistory'): if self.env['runtime']['memoryManager'].isIndexListEmpty('clipboardHistory'):
self.env['runtime']['outputManager'].presentText(_('clipboard empty'), interrupt=True) self.env['runtime']['outputManager'].presentText(_('clipboard empty'), interrupt=True)
return return
# Get current clipboard content
clipboard = self.env['runtime']['memoryManager'].getIndexListElement('clipboardHistory') clipboard = self.env['runtime']['memoryManager'].getIndexListElement('clipboardHistory')
user = self.env['general']['currUser']
# First try to find xclip in common locations # Remember original display environment variable if it exists
xclip_paths = [ originalDisplay = os.environ.get('DISPLAY', '')
'/usr/bin/xclip', success = False
'/bin/xclip',
'/usr/local/bin/xclip'
]
xclip_path = None # Try different display options
for path in xclip_paths: for i in range(10):
if os.path.isfile(path) and os.access(path, os.X_OK): display = f":{i}"
xclip_path = path try:
# Set display environment variable
os.environ['DISPLAY'] = display
# Attempt to set clipboard content
importlib.reload(pyperclip) # Weird workaround for some distros
pyperclip.copy(clipboard)
# If we get here without exception, we found a working display
success = True
break break
except Exception:
# Failed for this display, try next one
continue
if not xclip_path: # Restore original display setting
self.env['runtime']['outputManager'].presentText( if originalDisplay:
'xclip not found in common locations', os.environ['DISPLAY'] = originalDisplay
interrupt=True
)
return
for display in range(10):
p = Popen(
['su', user, '-p', '-c', f"{xclip_path} -d :{display} -selection clipboard"],
stdin=PIPE, stdout=PIPE, stderr=PIPE, preexec_fn=os.setpgrp
)
stdout, stderr = p.communicate(input=clipboard.encode('utf-8'))
self.env['runtime']['outputManager'].interruptOutput()
stderr = stderr.decode('utf-8')
stdout = stdout.decode('utf-8')
if stderr == '':
break
if stderr != '':
self.env['runtime']['outputManager'].presentText(stderr, soundIcon='', interrupt=False)
else: else:
self.env['runtime']['outputManager'].presentText('exported to the X session.', interrupt=True) os.environ.pop('DISPLAY', None)
# Notify the user of the result
if success:
self.env['runtime']['outputManager'].presentText(_('exported to the X session.'), interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(_('failed to export to X clipboard. No available display found.'), interrupt=True)
except Exception as e: except Exception as e:
self.env['runtime']['outputManager'].presentText(str(e), soundIcon='', interrupt=False) self.env['runtime']['outputManager'].presentText(str(e), soundIcon='', interrupt=False)
def setCallback(self, callback): def setCallback(self, callback):
pass pass
@@ -5,9 +5,11 @@
# By Chrys, Storm Dragon, and contributers. # By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug from fenrirscreenreader.core import debug
import subprocess, os import importlib
from subprocess import Popen, PIPE
import _thread import _thread
import pyperclip
import os
class command(): class command():
def __init__(self): def __init__(self):
pass pass
@@ -22,33 +24,41 @@ class command():
_thread.start_new_thread(self._threadRun , ()) _thread.start_new_thread(self._threadRun , ())
def _threadRun(self): def _threadRun(self):
try: try:
# Find xclip path # Remember original display environment variable if it exists
xclip_paths = ['/usr/bin/xclip', '/bin/xclip', '/usr/local/bin/xclip'] originalDisplay = os.environ.get('DISPLAY', '')
xclip_path = None clipboardContent = None
for path in xclip_paths:
if os.path.isfile(path) and os.access(path, os.X_OK): # Try different display options
xclip_path = path for i in range(10):
display = f":{i}"
try:
# Set display environment variable
os.environ['DISPLAY'] = display
# Attempt to get clipboard content
importlib.reload(pyperclip) # Weird workaround for some distros
clipboardContent = pyperclip.paste()
# If we get here without exception, we found a working display
if clipboardContent:
break break
if not xclip_path: except Exception:
self.env['runtime']['outputManager'].presentText('xclip not found in common locations', interrupt=True) # Failed for this display, try next one
return continue
xClipboard = ''
for display in range(10): # Restore original display setting
p = Popen('su ' + self.env['general']['currUser'] + ' -p -c "' + xclip_path + ' -d :' + str(display) + ' -o"', stdout=PIPE, stderr=PIPE, shell=True) if originalDisplay:
stdout, stderr = p.communicate() os.environ['DISPLAY'] = originalDisplay
self.env['runtime']['outputManager'].interruptOutput()
stderr = stderr.decode('utf-8')
xClipboard = stdout.decode('utf-8')
if (stderr == ''):
break
if stderr != '':
self.env['runtime']['outputManager'].presentText(stderr , soundIcon='', interrupt=False)
else: else:
self.env['runtime']['memoryManager'].addValueToFirstIndex('clipboardHistory', xClipboard) os.environ.pop('DISPLAY', None)
# Process the clipboard content if we found any
if clipboardContent and isinstance(clipboardContent, str):
self.env['runtime']['memoryManager'].addValueToFirstIndex('clipboardHistory', clipboardContent)
self.env['runtime']['outputManager'].presentText('Import to Clipboard', soundIcon='CopyToClipboard', interrupt=True) self.env['runtime']['outputManager'].presentText('Import to Clipboard', soundIcon='CopyToClipboard', interrupt=True)
self.env['runtime']['outputManager'].presentText(xClipboard, soundIcon='', interrupt=False) self.env['runtime']['outputManager'].presentText(clipboardContent, soundIcon='', interrupt=False)
else:
self.env['runtime']['outputManager'].presentText('No text found in clipboard or no accessible display', interrupt=True)
except Exception as e: except Exception as e:
self.env['runtime']['outputManager'].presentText(e , soundIcon='', interrupt=False) self.env['runtime']['outputManager'].presentText(str(e), soundIcon='', interrupt=False)
def setCallback(self, callback): def setCallback(self, callback):
pass pass
@@ -35,12 +35,18 @@ class command():
if not (self.env['runtime']['byteManager'].getLastByteKey() in [b'^[[A',b'^[[B']): if not (self.env['runtime']['byteManager'].getLastByteKey() in [b'^[[A',b'^[[B']):
return return
# Get the current cursor's line from both old and new content
prevLine = self.env['screen']['oldContentText'].split('\n')[self.env['screen']['newCursor']['y']] prevLine = self.env['screen']['oldContentText'].split('\n')[self.env['screen']['newCursor']['y']]
currLine = self.env['screen']['newContentText'].split('\n')[self.env['screen']['newCursor']['y']] currLine = self.env['screen']['newContentText'].split('\n')[self.env['screen']['newCursor']['y']]
is_blank = currLine.strip() == ''
if prevLine == currLine: if prevLine == currLine:
if self.env['screen']['newDelta'] != '': if self.env['screen']['newDelta'] != '':
return return
if not currLine.isspace():
announce = currLine
if not is_blank:
currPrompt = currLine.find('$') currPrompt = currLine.find('$')
rootPrompt = currLine.find('#') rootPrompt = currLine.find('#')
if currPrompt <= 0: if currPrompt <= 0:
@@ -55,13 +61,13 @@ class command():
else: else:
announce = currLine announce = currLine
if currLine.isspace(): if is_blank:
self.env['runtime']['outputManager'].presentText(_("blank"), soundIcon='EmptyLine', interrupt=True, flush=False) self.env['runtime']['outputManager'].presentText(_("blank"), soundIcon='EmptyLine', interrupt=True, flush=False)
else: else:
self.env['runtime']['outputManager'].presentText(announce, interrupt=True, flush=False) self.env['runtime']['outputManager'].presentText(announce, interrupt=True, flush=False)
self.env['commandsIgnore']['onScreenUpdate']['CHAR_DELETE_ECHO'] = True self.env['commandsIgnore']['onScreenUpdate']['CHAR_DELETE_ECHO'] = True
self.env['commandsIgnore']['onScreenUpdate']['CHAR_ECHO'] = True self.env['commandsIgnore']['onScreenUpdate']['CHAR_ECHO'] = True
self.env['commandsIgnore']['onScreenUpdate']['INCOMING_IGNORE'] = True self.env['commandsIgnore']['onScreenUpdate']['INCOMING_IGNORE'] = True
def setCallback(self, callback): def setCallback(self, callback):
pass pass
@@ -159,7 +159,13 @@ class commandManager():
self.env['runtime']['debug'].writeDebugOut("Loading script:" + fileName ,debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut("Loading script:" + fileName ,debug.debugLevel.ERROR)
self.env['runtime']['debug'].writeDebugOut(str(e),debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut(str(e),debug.debugLevel.ERROR)
continue continue
def shutdownCommands(self, section): def shutdownCommands(self, section):
# Check if the section exists in the commands dictionary
if section not in self.env['commands']:
self.env['runtime']['debug'].writeDebugOut("shutdownCommands: section not found:" + section, debug.debugLevel.WARNING)
return
for command in sorted(self.env['commands'][section]): for command in sorted(self.env['commands'][section]):
try: try:
self.env['commands'][section][command].shutdown() self.env['commands'][section][command].shutdown()
@@ -170,7 +176,7 @@ class commandManager():
continue continue
def executeSwitchTrigger(self, trigger, unLoadScript, loadScript): def executeSwitchTrigger(self, trigger, unLoadScript, loadScript):
if self.env['runtime']['screenManager'].isSuspendingScreen(): if self.env['runtime']['screenManager'].isIgnoredScreen():
return return
#unload #unload
oldScript = unLoadScript oldScript = unLoadScript
@@ -193,7 +199,7 @@ class commandManager():
def executeDefaultTrigger(self, trigger, force=False): def executeDefaultTrigger(self, trigger, force=False):
if not force: if not force:
if self.env['runtime']['screenManager'].isSuspendingScreen(): if self.env['runtime']['screenManager'].isIgnoredScreen():
return return
for command in sorted(self.env['commands'][trigger]): for command in sorted(self.env['commands'][trigger]):
if self.commandExists(command, trigger): if self.commandExists(command, trigger):
@@ -208,7 +214,7 @@ class commandManager():
self.env['runtime']['debug'].writeDebugOut("Executing trigger:" + trigger + "." + command + str(e) ,debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut("Executing trigger:" + trigger + "." + command + str(e) ,debug.debugLevel.ERROR)
def executeCommand(self, command, section = 'commands'): def executeCommand(self, command, section = 'commands'):
if self.env['runtime']['screenManager'].isSuspendingScreen(): if self.env['runtime']['screenManager'].isIgnoredScreen():
return return
if self.commandExists(command, section): if self.commandExists(command, section):
try: try:
@@ -11,6 +11,14 @@ class cursorManager():
pass pass
def initialize(self, environment): def initialize(self, environment):
self.env = environment self.env = environment
def shouldProcessNumpadCommands(self):
"""
Check if numpad commands should be processed based on numlock state
Return True if numlock is OFF (commands should work)
Return False if numlock is ON (let keys type numbers)
"""
# Return False if numlock is ON
return not self.env['input']['newNumLock']
def shutdown(self): def shutdown(self):
pass pass
def clearMarks(self): def clearMarks(self):
+7 -4
View File
@@ -52,12 +52,15 @@ class debugManager():
else: else:
if not self._fileOpened and fileMode: if not self._fileOpened and fileMode:
self.openDebugFile() self.openDebugFile()
timestamp = str(datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f'))
if onAnyLevel: if onAnyLevel:
msg = 'ANY '+ str(level) + ' ' + str(datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')) levelInfo = 'INFO ANY'
else: else:
msg = str(level) +' ' + str(datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f') levelInfo = str(level)
)
msg += ': ' + text # Changed order: text comes first, then level and timestamp
msg = text + ' - ' + levelInfo + ' ' + timestamp
if printMode: if printMode:
print(msg) print(msg)
if fileMode: if fileMode:
+1 -1
View File
@@ -55,7 +55,7 @@ class fenrirManager():
if self.environment['runtime']['inputManager'].noKeyPressed(): if self.environment['runtime']['inputManager'].noKeyPressed():
self.environment['runtime']['inputManager'].clearLastDeepInput() self.environment['runtime']['inputManager'].clearLastDeepInput()
if self.environment['runtime']['screenManager'].isSuspendingScreen(): if self.environment['runtime']['screenManager'].isIgnoredScreen():
self.environment['runtime']['inputManager'].writeEventBuffer() self.environment['runtime']['inputManager'].writeEventBuffer()
else: else:
if self.environment['runtime']['helpManager'].isTutorialMode(): if self.environment['runtime']['helpManager'].isTutorialMode():
@@ -42,6 +42,25 @@ class inputDriver():
if not self._initialized: if not self._initialized:
return True return True
return True return True
def forceUngrab(self):
"""Emergency method to release grabbed devices in case of failure"""
if not self._initialized:
return True
try:
# Try standard ungrab first
return self.ungrabAllDevices()
except Exception as e:
# Just log the failure and inform the user
if hasattr(self, 'env') and 'runtime' in self.env and 'debug' in self.env['runtime']:
self.env['runtime']['debug'].writeDebugOut(
f"Emergency device release failed: {str(e)}",
debug.debugLevel.ERROR
)
else:
print(f"Emergency device release failed: {str(e)}")
return False
def hasIDevices(self): def hasIDevices(self):
if not self._initialized: if not self._initialized:
return False return False
+80 -4
View File
@@ -49,6 +49,7 @@ class inputManager():
return event return event
def setExecuteDeviceGrab(self, newExecuteDeviceGrab = True): def setExecuteDeviceGrab(self, newExecuteDeviceGrab = True):
self.executeDeviceGrab = newExecuteDeviceGrab self.executeDeviceGrab = newExecuteDeviceGrab
def handleDeviceGrab(self, force = False): def handleDeviceGrab(self, force = False):
if force: if force:
self.setExecuteDeviceGrab() self.setExecuteDeviceGrab()
@@ -61,17 +62,38 @@ class inputManager():
if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'): if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'):
self.executeDeviceGrab = False self.executeDeviceGrab = False
return return
# Add maximum retries to prevent infinite loops
maxRetries = 5
retryCount = 0
grabTimeout = 3 # Timeout in seconds
startTime = time.time()
if self.env['runtime']['screenManager'].getCurrScreenIgnored(): if self.env['runtime']['screenManager'].getCurrScreenIgnored():
while not self.ungrabAllDevices(): while not self.ungrabAllDevices():
retryCount += 1
if retryCount >= maxRetries or (time.time() - startTime) > grabTimeout:
self.env['runtime']['debug'].writeDebugOut("Failed to ungrab devices after multiple attempts", debug.debugLevel.ERROR)
# Force a release of devices if possible through alternative means
try:
self.env['runtime']['inputDriver'].forceUngrab()
except:
pass
break
time.sleep(0.25) time.sleep(0.25)
self.env['runtime']['debug'].writeDebugOut("retry ungrabAllDevices " ,debug.debugLevel.WARNING) self.env['runtime']['debug'].writeDebugOut(f"retry ungrabAllDevices {retryCount}/{maxRetries}", debug.debugLevel.WARNING)
self.env['runtime']['debug'].writeDebugOut("All devices ungrabbed" ,debug.debugLevel.INFO)
else: else:
while not self.grabAllDevices(): while not self.grabAllDevices():
retryCount += 1
if retryCount >= maxRetries or (time.time() - startTime) > grabTimeout:
self.env['runtime']['debug'].writeDebugOut("Failed to grab devices after multiple attempts", debug.debugLevel.ERROR)
# Continue without grabbing input - limited functionality but not locked
break
time.sleep(0.25) time.sleep(0.25)
self.env['runtime']['debug'].writeDebugOut("retry grabAllDevices" ,debug.debugLevel.WARNING) self.env['runtime']['debug'].writeDebugOut(f"retry grabAllDevices {retryCount}/{maxRetries}", debug.debugLevel.WARNING)
self.env['runtime']['debug'].writeDebugOut("All devices grabbed" ,debug.debugLevel.INFO)
self.executeDeviceGrab = False self.executeDeviceGrab = False
def sendKeys(self, keyMacro): def sendKeys(self, keyMacro):
for e in keyMacro: for e in keyMacro:
key = '' key = ''
@@ -252,10 +274,32 @@ class inputManager():
def getCurrShortcut(self, inputSequence = None): def getCurrShortcut(self, inputSequence = None):
shortcut = [] shortcut = []
shortcut.append(self.env['input']['shortcutRepeat']) shortcut.append(self.env['input']['shortcutRepeat'])
numpadKeys = ['KEY_KP0', 'KEY_KP1', 'KEY_KP2', 'KEY_KP3', 'KEY_KP4',
'KEY_KP5', 'KEY_KP6', 'KEY_KP7', 'KEY_KP8', 'KEY_KP9',
'KEY_KPDOT', 'KEY_KPPLUS', 'KEY_KPMINUS', 'KEY_KPASTERISK',
'KEY_KPSLASH', 'KEY_KPENTER', 'KEY_KPEQUAL']
if inputSequence: if inputSequence:
# Check if any key in the sequence is a numpad key and numlock is ON
# If numlock is ON and any key in the sequence is a numpad key, return an empty shortcut
if not self.env['runtime']['cursorManager'].shouldProcessNumpadCommands():
for key in inputSequence:
if key in numpadKeys:
# Return an empty/invalid shortcut that won't match any command
return "[]"
shortcut.append(inputSequence) shortcut.append(inputSequence)
else: else:
# Same check for current input
if not self.env['runtime']['cursorManager'].shouldProcessNumpadCommands():
for key in self.env['input']['currInput']:
if key in numpadKeys:
# Return an empty/invalid shortcut that won't match any command
return "[]"
shortcut.append(self.env['input']['currInput']) shortcut.append(self.env['input']['currInput'])
if len(self.env['input']['prevInput']) < len(self.env['input']['currInput']): if len(self.env['input']['prevInput']) < len(self.env['input']['currInput']):
if self.env['input']['shortcutRepeat'] > 1 and not self.shortcutExists(str(shortcut)): if self.env['input']['shortcutRepeat'] > 1 and not self.shortcutExists(str(shortcut)):
shortcut = [] shortcut = []
@@ -345,3 +389,35 @@ class inputManager():
self.lastDetectedDevices =devices self.lastDetectedDevices =devices
def getLastDetectedDevices(self): def getLastDetectedDevices(self):
return self.lastDetectedDevices return self.lastDetectedDevices
def reloadShortcuts(self):
"""Reload keyboard shortcuts from current layout setting"""
# Clear existing bindings
self.env['bindings'].clear()
self.env['rawBindings'].clear()
# Get current layout path
layout_setting = self.env['runtime']['settingsManager'].getSetting('keyboard', 'keyboardLayout')
# Resolve full path if needed
if not os.path.exists(layout_setting):
settingsRoot = '/etc/fenrirscreenreader/'
if not os.path.exists(settingsRoot):
import fenrirscreenreader
fenrirPath = os.path.dirname(fenrirscreenreader.__file__)
settingsRoot = fenrirPath + '/../../config/'
layout_path = settingsRoot + 'keyboard/' + layout_setting + '.conf'
if not os.path.exists(layout_path):
# Fallback to default if layout not found
layout_path = settingsRoot + 'keyboard/desktop.conf'
else:
layout_path = layout_setting
# Reload shortcuts
self.loadShortcuts(layout_path)
self.env['runtime']['debug'].writeDebugOut(
"Reloaded shortcuts from: " + layout_path,
debug.debugLevel.INFO,
onAnyLevel=True
)
+6 -6
View File
@@ -59,7 +59,7 @@ class screenManager():
if self.isCurrScreenIgnoredChanged(): if self.isCurrScreenIgnoredChanged():
self.env['runtime']['inputManager'].setExecuteDeviceGrab() self.env['runtime']['inputManager'].setExecuteDeviceGrab()
self.env['runtime']['inputManager'].handleDeviceGrab() self.env['runtime']['inputManager'].handleDeviceGrab()
if not self.isSuspendingScreen(self.env['screen']['newTTY']): if not self.isIgnoredScreen(self.env['screen']['newTTY']):
self.update(eventData, 'onScreenChange') self.update(eventData, 'onScreenChange')
self.env['screen']['lastScreenUpdate'] = time.time() self.env['screen']['lastScreenUpdate'] = time.time()
else: else:
@@ -81,7 +81,7 @@ class screenManager():
return self.prevScreenIgnored return self.prevScreenIgnored
def updateScreenIgnored(self): def updateScreenIgnored(self):
self.prevScreenIgnored = self.currScreenIgnored self.prevScreenIgnored = self.currScreenIgnored
self.currScreenIgnored = self.isSuspendingScreen(self.env['screen']['newTTY']) self.currScreenIgnored = self.isIgnoredScreen(self.env['screen']['newTTY'])
def update(self, eventData, trigger='onUpdate'): def update(self, eventData, trigger='onUpdate'):
# set new "old" values # set new "old" values
self.env['screen']['oldContentBytes'] = self.env['screen']['newContentBytes'] self.env['screen']['oldContentBytes'] = self.env['screen']['newContentBytes']
@@ -174,16 +174,16 @@ class screenManager():
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('screenManager:update:highlight: ' + str(e),debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut('screenManager:update:highlight: ' + str(e),debug.debugLevel.ERROR)
def isSuspendingScreen(self, screen = None): def isIgnoredScreen(self, screen = None):
if screen == None: if screen == None:
screen = self.env['screen']['newTTY'] screen = self.env['screen']['newTTY']
ignoreScreens = [] ignoreScreens = []
fixIgnoreScreens = self.env['runtime']['settingsManager'].getSetting('screen', 'suspendingScreen') fixIgnoreScreens = self.env['runtime']['settingsManager'].getSetting('screen', 'ignoreScreen')
if fixIgnoreScreens != '': if fixIgnoreScreens != '':
ignoreScreens.extend(fixIgnoreScreens.split(',')) ignoreScreens.extend(fixIgnoreScreens.split(','))
if self.env['runtime']['settingsManager'].getSettingAsBool('screen', 'autodetectSuspendingScreen'): if self.env['runtime']['settingsManager'].getSettingAsBool('screen', 'autodetectIgnoreScreen'):
ignoreScreens.extend(self.env['screen']['autoIgnoreScreens']) ignoreScreens.extend(self.env['screen']['autoIgnoreScreens'])
self.env['runtime']['debug'].writeDebugOut('screenManager:isSuspendingScreen ignore:' + str(ignoreScreens) + ' current:'+ str(screen ), debug.debugLevel.INFO) self.env['runtime']['debug'].writeDebugOut('screenManager:isIgnoredScreen ignore:' + str(ignoreScreens) + ' current:'+ str(screen ), debug.debugLevel.INFO)
return (screen in ignoreScreens) return (screen in ignoreScreens)
def isScreenChange(self): def isScreenChange(self):
+2 -2
View File
@@ -40,8 +40,8 @@ settingsData = {
'driver': 'vcsaDriver', 'driver': 'vcsaDriver',
'encoding': 'auto', 'encoding': 'auto',
'screenUpdateDelay': 0.1, 'screenUpdateDelay': 0.1,
'suspendingScreen': '', 'ignoreScreen': '',
'autodetectSuspendingScreen': False, 'autodetectIgnoreScreen': False,
}, },
'general':{ 'general':{
'debugLevel': debug.debugLevel.DEACTIVE, 'debugLevel': debug.debugLevel.DEACTIVE,
+2 -2
View File
@@ -4,5 +4,5 @@
# Fenrir TTY screen reader # Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers. # By Chrys, Storm Dragon, and contributers.
version = "2024.12.20" version = "2025.04.28"
codeName = "stable" codeName = "master"
@@ -72,14 +72,16 @@ class driver(inputDriver):
self.env['runtime']['debug'].writeDebugOut('plugInputDeviceWatchdogUdev:' + str(device), debug.debugLevel.INFO) self.env['runtime']['debug'].writeDebugOut('plugInputDeviceWatchdogUdev:' + str(device), debug.debugLevel.INFO)
try: try:
try: try:
if device.name.upper() in ['','SPEAKUP','FENRIR-UINPUT']: # FIX: Check if attributes exist before accessing them
if hasattr(device, 'name') and device.name and device.name.upper() in ['','SPEAKUP','FENRIR-UINPUT']:
ignorePlug = True ignorePlug = True
if device.phys.upper() in ['','SPEAKUP','FENRIR-UINPUT']: if hasattr(device, 'phys') and device.phys and device.phys.upper() in ['','SPEAKUP','FENRIR-UINPUT']:
ignorePlug = True ignorePlug = True
if 'BRLTTY' in device.name.upper(): if hasattr(device, 'name') and device.name and 'BRLTTY' in device.name.upper():
ignorePlug = True ignorePlug = True
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut("plugInputDeviceWatchdogUdev CHECK NAME CRASH: " + str(e), debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut("plugInputDeviceWatchdogUdev CHECK NAME CRASH: " + str(e), debug.debugLevel.ERROR)
if not ignorePlug: if not ignorePlug:
virtual = '/sys/devices/virtual/input/' in device.sys_path virtual = '/sys/devices/virtual/input/' in device.sys_path
if device.device_node: if device.device_node:
@@ -89,7 +91,7 @@ class driver(inputDriver):
try: try:
pollTimeout = 1 pollTimeout = 1
device = monitor.poll(pollTimeout) device = monitor.poll(pollTimeout)
except: except Exception:
device = None device = None
ignorePlug = False ignorePlug = False
if validDevices: if validDevices:
@@ -146,7 +148,7 @@ class driver(inputDriver):
if uDevice: if uDevice:
if self.gDevices[iDevice.fd]: if self.gDevices[iDevice.fd]:
self.writeUInput(uDevice, event) self.writeUInput(uDevice, event)
except Exception as e: except Exception:
pass pass
def writeUInput(self, uDevice, event): def writeUInput(self, uDevice, event):
@@ -191,7 +193,7 @@ class driver(inputDriver):
try: try:
with open(deviceFile) as f: with open(deviceFile) as f:
pass pass
except Exception as e: except Exception:
continue continue
# 3 pos absolute # 3 pos absolute
# 2 pos relative # 2 pos relative
@@ -201,11 +203,12 @@ class driver(inputDriver):
except: except:
continue continue
try: try:
if currDevice.name.upper() in ['','SPEAKUP','FENRIR-UINPUT']: # FIX: Check if attributes exist before accessing them
if hasattr(currDevice, 'name') and currDevice.name and currDevice.name.upper() in ['', 'SPEAKUP', 'FENRIR-UINPUT']:
continue continue
if currDevice.phys.upper() in ['','SPEAKUP','FENRIR-UINPUT']: if hasattr(currDevice, 'phys') and currDevice.phys and currDevice.phys.upper() in ['', 'SPEAKUP', 'FENRIR-UINPUT']:
continue continue
if 'BRLTTY' in currDevice.name.upper(): if hasattr(currDevice, 'name') and currDevice.name and 'BRLTTY' in currDevice.name.upper():
continue continue
except: except:
pass pass
@@ -233,7 +236,11 @@ class driver(inputDriver):
self.addDevice(currDevice) self.addDevice(currDevice)
self.env['runtime']['debug'].writeDebugOut('Device added (Name):' + self.iDevices[currDevice.fd].name, debug.debugLevel.INFO) self.env['runtime']['debug'].writeDebugOut('Device added (Name):' + self.iDevices[currDevice.fd].name, debug.debugLevel.INFO)
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut("Device Skipped (Exception): " + deviceFile +' ' + currDevice.name +' '+ str(e),debug.debugLevel.INFO) try:
device_name = currDevice.name if hasattr(currDevice, 'name') else "unknown"
self.env['runtime']['debug'].writeDebugOut("Device Skipped (Exception): " + deviceFile + ' ' + device_name + ' ' + str(e), debug.debugLevel.INFO)
except:
self.env['runtime']['debug'].writeDebugOut("Device Skipped (Exception): " + deviceFile + ' ' + str(e), debug.debugLevel.INFO)
self.iDeviceNo = len(evdev.list_devices()) self.iDeviceNo = len(evdev.list_devices())
self.updateMPiDevicesFD() self.updateMPiDevicesFD()
@@ -247,6 +254,7 @@ class driver(inputDriver):
self.iDevicesFD.remove(fd) self.iDevicesFD.remove(fd)
except: except:
pass pass
def mapEvent(self, event): def mapEvent(self, event):
if not self._initialized: if not self._initialized:
return None return None
@@ -268,7 +276,7 @@ class driver(inputDriver):
mEvent['EventState'] = event.value mEvent['EventState'] = event.value
mEvent['EventType'] = event.type mEvent['EventType'] = event.type
return mEvent return mEvent
except Exception as e: except Exception:
return None return None
def getLedState(self, led=0): def getLedState(self, led=0):
@@ -281,6 +289,7 @@ class driver(inputDriver):
if led in dev.leds(): if led in dev.leds():
return True return True
return False return False
def toggleLedState(self, led=0): def toggleLedState(self, led=0):
if not self.hasIDevices(): if not self.hasIDevices():
return False return False
@@ -293,6 +302,7 @@ class driver(inputDriver):
self.iDevices[i].set_led(led, 0) self.iDevices[i].set_led(led, 0)
else: else:
self.iDevices[i].set_led(led, 1) self.iDevices[i].set_led(led, 1)
def grabAllDevices(self): def grabAllDevices(self):
if not self._initialized: if not self._initialized:
return True return True
@@ -301,6 +311,7 @@ class driver(inputDriver):
if not self.gDevices[fd]: if not self.gDevices[fd]:
ok = ok and self.grabDevice(fd) ok = ok and self.grabDevice(fd)
return ok return ok
def ungrabAllDevices(self): def ungrabAllDevices(self):
if not self._initialized: if not self._initialized:
return True return True
@@ -309,6 +320,7 @@ class driver(inputDriver):
if self.gDevices[fd]: if self.gDevices[fd]:
ok = ok and self.ungrabDevice(fd) ok = ok and self.ungrabDevice(fd)
return ok return ok
def createUInputDev(self, fd): def createUInputDev(self, fd):
if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'): if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'):
self.uDevices[fd] = None self.uDevices[fd] = None
@@ -336,6 +348,7 @@ class driver(inputDriver):
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: init Uinput not possible: ' + str(e), debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: init Uinput not possible: ' + str(e), debug.debugLevel.ERROR)
return return
def addDevice(self, newDevice): def addDevice(self, newDevice):
self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: device added: ' + str(newDevice.fd) + ' ' + str(newDevice), debug.debugLevel.INFO) self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: device added: ' + str(newDevice.fd) + ' ' + str(newDevice), debug.debugLevel.INFO)
try: try:
@@ -360,6 +373,9 @@ class driver(inputDriver):
def grabDevice(self, fd): def grabDevice(self, fd):
if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'): if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'):
return True return True
# FIX: Handle exception variable scope correctly
grab_error = None
try: try:
self.iDevices[fd].grab() self.iDevices[fd].grab()
self.gDevices[fd] = True self.gDevices[fd] = True
@@ -371,19 +387,26 @@ class driver(inputDriver):
try: try:
self.uDevices[fd].write(e.EV_KEY, key, 0) # 0 = key up self.uDevices[fd].write(e.EV_KEY, key, 0) # 0 = key up
self.uDevices[fd].syn() self.uDevices[fd].syn()
except Exception as e: except Exception as mod_error:
self.env['runtime']['debug'].writeDebugOut('Failed to reset modifier key: ' + str(e), debug.debugLevel.WARNING) self.env['runtime']['debug'].writeDebugOut('Failed to reset modifier key: ' + str(mod_error), debug.debugLevel.WARNING)
except IOError: except IOError:
if not self.gDevices[fd]: if not self.gDevices[fd]:
return False return False
except Exception as e: except Exception as ex:
self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: grabing not possible: ' + str(e),debug.debugLevel.ERROR) grab_error = ex
if grab_error:
self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: grabing not possible: ' + str(grab_error), debug.debugLevel.ERROR)
return False return False
return True return True
def ungrabDevice(self, fd): def ungrabDevice(self, fd):
if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'): if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'):
return True return True
# FIX: Handle exception variable scope correctly
ungrab_error = None
try: try:
self.iDevices[fd].ungrab() self.iDevices[fd].ungrab()
self.gDevices[fd] = False self.gDevices[fd] = False
@@ -391,11 +414,15 @@ class driver(inputDriver):
except IOError: except IOError:
if self.gDevices[fd]: if self.gDevices[fd]:
return False return False
# self.gDevices[fd] = False except Exception as ex:
# #self.removeDevice(fd) ungrab_error = ex
except Exception as e:
if ungrab_error:
self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: ungrabing not possible: ' + str(ungrab_error), debug.debugLevel.ERROR)
return False return False
return True return True
def removeDevice(self, fd): def removeDevice(self, fd):
self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: device removed: ' + str(fd) + ' ' + str(self.iDevices[fd]), debug.debugLevel.INFO) self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: device removed: ' + str(fd) + ' ' + str(self.iDevices[fd]), debug.debugLevel.INFO)
self.clearEventBuffer() self.clearEventBuffer()