50 Commits
Author SHA1 Message Date
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
Storm Dragon 9da925c80d Fixed merge conflict. 2024-12-21 17:52:38 -05:00
Storm Dragon 9c45ca1b5f Fixed bug in Fenrir configuration tool. 2024-12-21 17:46:23 -05:00
Storm Dragon d1be848ebb Merge branch 'testing' got rid of the scm dependency." 2024-12-20 14:11:51 -05:00
Storm Dragon bc4319bf5e Forgot to get rid of the scm stuff since moving back to setup.py. 2024-12-20 14:08:25 -05:00
Storm Dragon 6f8139ed8f Change codename to stable for tag. 2024-12-20 13:33:23 -05:00
Storm Dragon f1a6c75459 Merged setup fixes to master. Went back to setup.py just with some updates to minimize python's grumbling. Also, merged the locale setup. 2024-12-20 12:50:32 -05:00
Storm Dragon 1d746eb709 Minimal pyproject.tmol added. 2024-12-20 12:30:56 -05:00
Storm Dragon 80f549fde9 Hopefully final fix to setup. 2024-12-20 12:24:52 -05:00
Storm Dragon 9f57f7faec Revert back to setup.py because it worked. 2024-12-20 12:19:09 -05:00
Storm Dragon 099d49d670 maybe was over complicating things. 2024-12-20 12:00:23 -05:00
Storm Dragon 67b6c79678 Trying some debugging to figure out what's going wrong. 2024-12-20 11:43:43 -05:00
Storm Dragon 191181a6a5 This modern installation method is turning out to be a pita. 2024-12-20 11:32:26 -05:00
Storm Dragon c77d2bddd8 Second attempt at placing files into correct directories. 2024-12-20 11:22:09 -05:00
Storm Dragon 90d8e62db0 Fixed a bug in setup.py. 2024-12-20 11:09:32 -05:00
Storm Dragon d6a9332f80 Modernize installation. 2024-12-20 11:04:13 -05:00
Storm Dragon 55ce73322b File restructuring. 2024-12-20 10:09:07 -05:00
Storm Dragon 78ca59a938 Found non working unused python directory. Can add it back if needed. 2024-12-20 09:42:47 -05:00
Storm Dragon dd52d08171 Copy translation files into place with setup. 2024-12-20 08:57:27 -05:00
Storm Dragon 29a2db0e0c Fixed a bug that required the -f flag in conjunction with -e to use pty. 2024-12-15 21:27:39 -05:00
Storm Dragon b6201235e6 Removed the emacs speech driver. Also some cleanup missed from removing the old espeak driver that no longer worked. 2024-12-11 17:31:05 -05:00
Storm Dragon f7584463e3 Changed default speech driver to speech-dispatcher. Lowered default speech rate a bit. 2024-12-11 08:46:22 -05:00
162 changed files with 4833 additions and 4849 deletions
+30 -25
View File
@@ -46,28 +46,33 @@ _:===:Lienie unten
[customDict]
[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*|'|"]<{-.-}>:===:Raves
regex;[\s*|'|"][\s*|'|"]8-\):===:smile with sunglasses
regex;[\s*|'|"][\s*|'|"]:-/:===:confused
regex;[\s*|'|"][\s*|'|"]-\.-:===:bugged
regex;[\s*|'|"][\s*|'|"]>\.<:===:laughing
regex;[\s*|'|"][\s*|'|"]8-X:===:skull
regex;[\s*|'|"][\s*|'|"]>:\):===:evil smile
regex;[\s*|'|"][\s*|'|"]>:-\):===:evil smile
regex;[\s*|'|"][\s*|'|"]\\o/:===:Hurray
regex;[\s*|'|"][\s*|'|"]:/:===:confused
regex;[\s*|'|"][\s*|'|"]8\):===:smile with sunglasses
regex;[\s*|'|"]:D[\s*|'|"]:===:laugh
regex;[\s*|'|"];\)[\s*|'|"]:===:wink
regex;[\s*|'|"]XD[\s*|'|"]:===:LOL
regex;[\s*|'|"]:-\)[\s*|'|"]:===:smile
regex;[\s*|'|"]:\)[\s*|'|"]:===:smile
regex;[\s*|'|"]->[\s*|'|"]:===:arrow right
# example for arrow left
#(?:[ |^])(<-)(?:[ ,.!?$]):===:arrow left
# or
#([ |^])<-([ ,.!?$]):===:arrow left\2
regex;[\s*|'|"]<-[\s*|'|"]:===:arrow left
regex;[\s*|'|"][O|o][O|o][\s*|'|"]:===:WTF?
regex;[\s*|'|"]\^\^[\s*|'|"]:===:enjoy smile
regex;(?:^|\s)(?::-\)|:\))(?:\s|$):===:smile
regex;(?:^|\s)(?::-\(|:\()(?:\s|$):===:frown
regex;(?:^|\s)(?::-D|:D)(?:\s|$):===:big grin
regex;(?:^|\s)(?:;-\)|;\))(?:\s|$):===:wink
regex;(?:^|\s)(?::-P|:P)(?:\s|$):===:tongue out
regex;(?:^|\s)(?::-O|:O)(?:\s|$):===:surprised
regex;(?:^|\s)(?::-/|:/)(?:\s|$):===:skeptical
regex;(?:^|\s)(?:>:-\(|>:\()(?:\s|$):===:angry
regex;(?:^|\s)(?::'\(|:'-\()(?:\s|$):===:crying
regex;(?:^|\s)(?::-\||:\|)(?:\s|$):===:neutral
regex;(?:^|\s)(?:8-\)|B-\))(?:\s|$):===:cool
regex;(?:^|\s)8-X(?:\s|$):===:skull and crossbones
regex;(?:^|\s)(?:X-X|x-x)(?:\s|$):===:knocked out
regex;(?:^|\s)(?::-X|:-x|:X|:x)(?:\s|$):===:lips sealed
regex;(?:^|\s)XD(?:\s|$):===:laughing hard
regex;(?:^|\s)\^_\^(?:\s|$):===:happy
regex;(?:^|\s)-_-(?:\s|$):===:annoyed
regex;(?:^|\s)o_O(?:\s|$):===:confused
regex;(?:^|\s)>_<(?:\s|$):===:frustrated
regex;(?:^|\s)\\o/(?:\s|$):===:hurray
regex;(?:^|\s)<3(?:\s|$):===:heart
regex;(?:^|\s)</3(?:\s|$):===:broken heart
regex;(?:^|\s)(?:Oo|oO)(?:\s|$):===:WTF?
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
@@ -4,7 +4,7 @@
# the entrys are seperated with :===: in words colon tripple equal colon ( to not collide with substitutions)
[levelDict]
none:===:
some:===:-$~+*-/\@#
some:===:-$~+*-/\@#_
most:===:.,:-$~+*-/\@!#%^&*()[]}{<>;
all:===:!"#$%& \'()*+,-./:;<=>?@[\\]^_`{|}~
@@ -46,27 +46,33 @@ _:===:line
[customDict]
[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*|'|"]<{-.-}>:===:Raves
regex;[\s*|'|"][\s*|'|"]8-\):===:smile with sunglasses
regex;[\s*|'|"][\s*|'|"]:-/:===:confused
regex;[\s*|'|"][\s*|'|"]-\.-:===:bugged
regex;[\s*|'|"][\s*|'|"]>\.<:===:laughing
regex;[\s*|'|"][\s*|'|"]8-X:===:skull
regex;[\s*|'|"][\s*|'|"]>:\):===:evil smile
regex;[\s*|'|"][\s*|'|"]>:-\):===:evil smile
regex;[\s*|'|"][\s*|'|"]\\o/:===:Hurray
regex;[\s*|'|"][\s*|'|"]:/:===:confused
regex;[\s*|'|"]:D[\s*|'|"]:===:laugh
regex;[\s*|'|"];\)[\s*|'|"]:===:wink
regex;[\s*|'|"]XD[\s*|'|"]:===:LOL
regex;[\s*|'|"]:-\)[\s*|'|"]:===:smile
regex;[\s*|'|"]:\)[\s*|'|"]:===:smile
regex;[\s*|'|"]->[\s*|'|"]:===:arrow right
# example for arrow left
#(?:[ |^])(<-)(?:[ ,.!?$]):===:arrow left
# or
#([ |^])<-([ ,.!?$]):===:arrow left\2
regex;[\s*|'|"]<-[\s*|'|"]:===:arrow left
regex;[\s+|'|"](Oo)|(oO)[\s+|'|"]:===:WTF?
regex;[\s*|'|"]\^\^[\s*|'|"]:===:enjoy smile
regex;(?:^|\s)(?::-\)|:\))(?:\s|$):===:smile
regex;(?:^|\s)(?::-\(|:\()(?:\s|$):===:frown
regex;(?:^|\s)(?::-D|:D)(?:\s|$):===:big grin
regex;(?:^|\s)(?:;-\)|;\))(?:\s|$):===:wink
regex;(?:^|\s)(?::-P|:P)(?:\s|$):===:tongue out
regex;(?:^|\s)(?::-O|:O)(?:\s|$):===:surprised
regex;(?:^|\s)(?::-/|:/)(?:\s|$):===:skeptical
regex;(?:^|\s)(?:>:-\(|>:\()(?:\s|$):===:angry
regex;(?:^|\s)(?::'\(|:'-\()(?:\s|$):===:crying
regex;(?:^|\s)(?::-\||:\|)(?:\s|$):===:neutral
regex;(?:^|\s)(?:8-\)|B-\))(?:\s|$):===:cool
regex;(?:^|\s)8-X(?:\s|$):===:skull and crossbones
regex;(?:^|\s)(?:X-X|x-x)(?:\s|$):===:knocked out
regex;(?:^|\s)(?::-X|:-x|:X|:x)(?:\s|$):===:lips sealed
regex;(?:^|\s)XD(?:\s|$):===:laughing hard
regex;(?:^|\s)\^_\^(?:\s|$):===:happy
regex;(?:^|\s)-_-(?:\s|$):===:annoyed
regex;(?:^|\s)o_O(?:\s|$):===:confused
regex;(?:^|\s)>_<(?:\s|$):===:frustrated
regex;(?:^|\s)\\o/(?:\s|$):===:hurray
regex;(?:^|\s)<3(?:\s|$):===:heart
regex;(?:^|\s)</3(?:\s|$):===:broken heart
regex;(?:^|\s)(?:Oo|oO)(?:\s|$):===:WTF?
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
@@ -4,7 +4,7 @@
# the entrys are seperated with :===: in words colon tripple equal colon ( to not collide with substitutions)
[levelDict]
none:===:
some:===:-$~+*-/\@
some:===:-$~+*-/\@_
most:===:.,:-$~+*-/\@!#%^&*()[]}{<>;
all:===:!"#$%& \'()*+,-./:;<=>?@[\\]^_`{|}~
@@ -46,27 +46,33 @@ _:===:line
[customDict]
[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*|'|"]<{-.-}>:===:Raves
regex;[\s*|'|"][\s*|'|"]8-\):===:smile with sunglasses
regex;[\s*|'|"][\s*|'|"]:-/:===:confused
regex;[\s*|'|"][\s*|'|"]-\.-:===:bugged
regex;[\s*|'|"][\s*|'|"]>\.<:===:laughing
regex;[\s*|'|"][\s*|'|"]8-X:===:skull
regex;[\s*|'|"][\s*|'|"]>:\):===:evil smile
regex;[\s*|'|"][\s*|'|"]>:-\):===:evil smile
regex;[\s*|'|"][\s*|'|"]\\o/:===:Hurray
regex;[\s*|'|"][\s*|'|"]:/:===:confused
regex;[\s*|'|"]:D[\s*|'|"]:===:laugh
regex;[\s*|'|"];\)[\s*|'|"]:===:wink
regex;[\s*|'|"]XD[\s*|'|"]:===:LOL
regex;[\s*|'|"]:-\)[\s*|'|"]:===:smile
regex;[\s*|'|"]:\)[\s*|'|"]:===:smile
regex;[\s*|'|"]->[\s*|'|"]:===:arrow right
# example for arrow left
#(?:[ |^])(<-)(?:[ ,.!?$]):===:arrow left
# or
#([ |^])<-([ ,.!?$]):===:arrow left\2
regex;[\s*|'|"]<-[\s*|'|"]:===:arrow left
regex;[\s+|'|"][O|o][O|o][\s+|'|"]:===:WTF?
regex;[\s*|'|"]\^\^[\s*|'|"]:===:enjoy smile
regex;(?:^|\s)(?::-\)|:\))(?:\s|$):===:smile
regex;(?:^|\s)(?::-\(|:\()(?:\s|$):===:frown
regex;(?:^|\s)(?::-D|:D)(?:\s|$):===:big grin
regex;(?:^|\s)(?:;-\)|;\))(?:\s|$):===:wink
regex;(?:^|\s)(?::-P|:P)(?:\s|$):===:tongue out
regex;(?:^|\s)(?::-O|:O)(?:\s|$):===:surprised
regex;(?:^|\s)(?::-/|:/)(?:\s|$):===:skeptical
regex;(?:^|\s)(?:>:-\(|>:\()(?:\s|$):===:angry
regex;(?:^|\s)(?::'\(|:'-\()(?:\s|$):===:crying
regex;(?:^|\s)(?::-\||:\|)(?:\s|$):===:neutral
regex;(?:^|\s)(?:8-\)|B-\))(?:\s|$):===:cool
regex;(?:^|\s)8-X(?:\s|$):===:skull and crossbones
regex;(?:^|\s)(?:X-X|x-x)(?:\s|$):===:knocked out
regex;(?:^|\s)(?::-X|:-x|:X|:x)(?:\s|$):===:lips sealed
regex;(?:^|\s)XD(?:\s|$):===:laughing hard
regex;(?:^|\s)\^_\^(?:\s|$):===:happy
regex;(?:^|\s)-_-(?:\s|$):===:annoyed
regex;(?:^|\s)o_O(?:\s|$):===:confused
regex;(?:^|\s)>_<(?:\s|$):===:frustrated
regex;(?:^|\s)\\o/(?:\s|$):===:hurray
regex;(?:^|\s)<3(?:\s|$):===:heart
regex;(?:^|\s)</3(?:\s|$):===:broken heart
regex;(?:^|\s)(?:Oo|oO)(?:\s|$):===:WTF?
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]
[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*|'|"]<{-.-}>:===:Raves
regex;[\s*|'|"][\s*|'|"]8-\):===:smile with sunglasses
regex;[\s*|'|"][\s*|'|"]:-/:===:confused
regex;[\s*|'|"][\s*|'|"]-\.-:===:bugged
regex;[\s*|'|"][\s*|'|"]>\.<:===:laughing
regex;[\s*|'|"][\s*|'|"]8-X:===:skull
regex;[\s*|'|"][\s*|'|"]>:\):===:evil smile
regex;[\s*|'|"][\s*|'|"]>:-\):===:evil smile
regex;[\s*|'|"][\s*|'|"]\\o/:===:Hurray
regex;[\s*|'|"][\s*|'|"]:/:===:confused
regex;[\s*|'|"][\s*|'|"]8\):===:smile with sunglasses
regex;[\s*|'|"]:D[\s*|'|"]:===:laugh
regex;[\s*|'|"];\)[\s*|'|"]:===:wink
regex;[\s*|'|"]XD[\s*|'|"]:===:LOL
regex;[\s*|'|"]:-\)[\s*|'|"]:===:smile
regex;[\s*|'|"]:\)[\s*|'|"]:===:smile
regex;[\s*|'|"]->[\s*|'|"]:===:arrow right
# example for arrow left
#(?:[ |^])(<-)(?:[ ,.!?$]):===:arrow left
# or
#([ |^])<-([ ,.!?$]):===:arrow left\2
regex;[\s*|'|"]<-[\s*|'|"]:===:arrow left
regex;[\s*|'|"][O|o][O|o][\s*|'|"]:===:WTF?
regex;[\s*|'|"]\^\^[\s*|'|"]:===:enjoy smile
regex;(?:^|\s)(?::-\)|:\))(?:\s|$):===:smile
regex;(?:^|\s)(?::-\(|:\()(?:\s|$):===:frown
regex;(?:^|\s)(?::-D|:D)(?:\s|$):===:big grin
regex;(?:^|\s)(?:;-\)|;\))(?:\s|$):===:wink
regex;(?:^|\s)(?::-P|:P)(?:\s|$):===:tongue out
regex;(?:^|\s)(?::-O|:O)(?:\s|$):===:surprised
regex;(?:^|\s)(?::-/|:/)(?:\s|$):===:skeptical
regex;(?:^|\s)(?:>:-\(|>:\()(?:\s|$):===:angry
regex;(?:^|\s)(?::'\(|:'-\()(?:\s|$):===:crying
regex;(?:^|\s)(?::-\||:\|)(?:\s|$):===:neutral
regex;(?:^|\s)(?:8-\)|B-\))(?:\s|$):===:cool
regex;(?:^|\s)8-X(?:\s|$):===:skull and crossbones
regex;(?:^|\s)(?:X-X|x-x)(?:\s|$):===:knocked out
regex;(?:^|\s)(?::-X|:-x|:X|:x)(?:\s|$):===:lips sealed
regex;(?:^|\s)XD(?:\s|$):===:laughing hard
regex;(?:^|\s)\^_\^(?:\s|$):===:happy
regex;(?:^|\s)-_-(?:\s|$):===:annoyed
regex;(?:^|\s)o_O(?:\s|$):===:confused
regex;(?:^|\s)>_<(?:\s|$):===:frustrated
regex;(?:^|\s)\\o/(?:\s|$):===:hurray
regex;(?:^|\s)<3(?:\s|$):===:heart
regex;(?:^|\s)</3(?:\s|$):===:broken heart
regex;(?:^|\s)(?:Oo|oO)(?:\s|$):===:WTF?
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 @@ _:===:souligné
[customDict]
[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*|'|"]<{-.-}>:===:Raves
regex;[\s*|'|"][\s*|'|"]8-\):===:smile with sunglasses
regex;[\s*|'|"][\s*|'|"]:-/:===:confused
regex;[\s*|'|"][\s*|'|"]-\.-:===:bugged
regex;[\s*|'|"][\s*|'|"]>\.<:===:laughing
regex;[\s*|'|"][\s*|'|"]8-X:===:skull
regex;[\s*|'|"][\s*|'|"]>:\):===:evil smile
regex;[\s*|'|"][\s*|'|"]>:-\):===:evil smile
regex;[\s*|'|"][\s*|'|"]\\o/:===:Hurray
regex;[\s*|'|"][\s*|'|"]:/:===:confused
regex;[\s*|'|"][\s*|'|"]8\):===:smile with sunglasses
regex;[\s*|'|"]:D[\s*|'|"]:===:laugh
regex;[\s*|'|"];\)[\s*|'|"]:===:wink
regex;[\s*|'|"]XD[\s*|'|"]:===:LOL
regex;[\s*|'|"]:-\)[\s*|'|"]:===:smile
regex;[\s*|'|"]:\)[\s*|'|"]:===:smile
regex;[\s*|'|"]->[\s*|'|"]:===:arrow right
# example for arrow left
#(?:[ |^])(<-)(?:[ ,.!?$]):===:arrow left
# or
#([ |^])<-([ ,.!?$]):===:arrow left\2
regex;[\s*|'|"]<-[\s*|'|"]:===:arrow left
regex;[\s*|'|"][O|o][O|o][\s*|'|"]:===:WTF?
regex;[\s*|'|"]\^\^[\s*|'|"]:===:enjoy smile
regex;(?:^|\s)(?::-\)|:\))(?:\s|$):===:smile
regex;(?:^|\s)(?::-\(|:\()(?:\s|$):===:frown
regex;(?:^|\s)(?::-D|:D)(?:\s|$):===:big grin
regex;(?:^|\s)(?:;-\)|;\))(?:\s|$):===:wink
regex;(?:^|\s)(?::-P|:P)(?:\s|$):===:tongue out
regex;(?:^|\s)(?::-O|:O)(?:\s|$):===:surprised
regex;(?:^|\s)(?::-/|:/)(?:\s|$):===:skeptical
regex;(?:^|\s)(?:>:-\(|>:\()(?:\s|$):===:angry
regex;(?:^|\s)(?::'\(|:'-\()(?:\s|$):===:crying
regex;(?:^|\s)(?::-\||:\|)(?:\s|$):===:neutral
regex;(?:^|\s)(?:8-\)|B-\))(?:\s|$):===:cool
regex;(?:^|\s)8-X(?:\s|$):===:skull and crossbones
regex;(?:^|\s)(?:X-X|x-x)(?:\s|$):===:knocked out
regex;(?:^|\s)(?::-X|:-x|:X|:x)(?:\s|$):===:lips sealed
regex;(?:^|\s)XD(?:\s|$):===:laughing hard
regex;(?:^|\s)\^_\^(?:\s|$):===:happy
regex;(?:^|\s)-_-(?:\s|$):===:annoyed
regex;(?:^|\s)o_O(?:\s|$):===:confused
regex;(?:^|\s)>_<(?:\s|$):===:frustrated
regex;(?:^|\s)\\o/(?:\s|$):===:hurray
regex;(?:^|\s)<3(?:\s|$):===:heart
regex;(?:^|\s)</3(?:\s|$):===:broken heart
regex;(?:^|\s)(?:Oo|oO)(?:\s|$):===:WTF?
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 @@ _:===:podkreślnik
[customDict]
[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*|'|"]<{-.-}>:===:Raves
regex;[\s*|'|"][\s*|'|"]8-\):===:smile with sunglasses
regex;[\s*|'|"][\s*|'|"]:-/:===:confused
regex;[\s*|'|"][\s*|'|"]-\.-:===:bugged
regex;[\s*|'|"][\s*|'|"]>\.<:===:laughing
regex;[\s*|'|"][\s*|'|"]8-X:===:skull
regex;[\s*|'|"][\s*|'|"]>:\):===:evil smile
regex;[\s*|'|"][\s*|'|"]>:-\):===:evil smile
regex;[\s*|'|"][\s*|'|"]\\o/:===:Hurray
regex;[\s*|'|"][\s*|'|"]:/:===:confused
regex;[\s*|'|"][\s*|'|"]8\):===:smile with sunglasses
regex;[\s*|'|"]:D[\s*|'|"]:===:laugh
regex;[\s*|'|"];\)[\s*|'|"]:===:wink
regex;[\s*|'|"]XD[\s*|'|"]:===:LOL
regex;[\s*|'|"]:-\)[\s*|'|"]:===:smile
regex;[\s*|'|"]:\)[\s*|'|"]:===:smile
regex;[\s*|'|"]->[\s*|'|"]:===:arrow right
# example for arrow left
#(?:[ |^])(<-)(?:[ ,.!?$]):===:arrow left
# or
#([ |^])<-([ ,.!?$]):===:arrow left\2
regex;[\s*|'|"]<-[\s*|'|"]:===:arrow left
regex;[\s*|'|"][O|o][O|o][\s*|'|"]:===:WTF?
regex;[\s*|'|"]\^\^[\s*|'|"]:===:enjoy smile
regex;(?:^|\s)(?::-\)|:\))(?:\s|$):===:smile
regex;(?:^|\s)(?::-\(|:\()(?:\s|$):===:frown
regex;(?:^|\s)(?::-D|:D)(?:\s|$):===:big grin
regex;(?:^|\s)(?:;-\)|;\))(?:\s|$):===:wink
regex;(?:^|\s)(?::-P|:P)(?:\s|$):===:tongue out
regex;(?:^|\s)(?::-O|:O)(?:\s|$):===:surprised
regex;(?:^|\s)(?::-/|:/)(?:\s|$):===:skeptical
regex;(?:^|\s)(?:>:-\(|>:\()(?:\s|$):===:angry
regex;(?:^|\s)(?::'\(|:'-\()(?:\s|$):===:crying
regex;(?:^|\s)(?::-\||:\|)(?:\s|$):===:neutral
regex;(?:^|\s)(?:8-\)|B-\))(?:\s|$):===:cool
regex;(?:^|\s)8-X(?:\s|$):===:skull and crossbones
regex;(?:^|\s)(?:X-X|x-x)(?:\s|$):===:knocked out
regex;(?:^|\s)(?::-X|:-x|:X|:x)(?:\s|$):===:lips sealed
regex;(?:^|\s)XD(?:\s|$):===:laughing hard
regex;(?:^|\s)\^_\^(?:\s|$):===:happy
regex;(?:^|\s)-_-(?:\s|$):===:annoyed
regex;(?:^|\s)o_O(?:\s|$):===:confused
regex;(?:^|\s)>_<(?:\s|$):===:frustrated
regex;(?:^|\s)\\o/(?:\s|$):===:hurray
regex;(?:^|\s)<3(?:\s|$):===:heart
regex;(?:^|\s)</3(?:\s|$):===:broken heart
regex;(?:^|\s)(?:Oo|oO)(?:\s|$):===:WTF?
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
@@ -4,7 +4,7 @@
# the entrys are seperated with :===: in words colon tripple equal colon ( to not collide with substitutions)
[levelDict]
none:===:
some:===:-$~+*-/\@
some:===:-$~+*-/\@_
most:===:.,:-$~+*-/\@!#%^&*()[]}{<>;
all:===:!"#$%& \'()*+,-./:;<=>?@[\\]^_`{|}~
@@ -52,27 +52,33 @@ regex;(?i)shit:===:shitt
regex;(?i)slither:===:slitther
[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*|'|"]<{-.-}>:===:Raves
regex;[\s*|'|"][\s*|'|"]8-\):===:smile with sunglasses
regex;[\s*|'|"][\s*|'|"]:-/:===:confused
regex;[\s*|'|"][\s*|'|"]-\.-:===:bugged
regex;[\s*|'|"][\s*|'|"]>\.<:===:laughing
regex;[\s*|'|"][\s*|'|"]8-X:===:skull
regex;[\s*|'|"][\s*|'|"]>:\):===:evil smile
regex;[\s*|'|"][\s*|'|"]>:-\):===:evil smile
regex;[\s*|'|"][\s*|'|"]\\o/:===:Hurray
regex;[\s*|'|"][\s*|'|"]:/:===:confused
regex;[\s*|'|"]:D[\s*|'|"]:===:laugh
regex;[\s*|'|"];\)[\s*|'|"]:===:wink
regex;[\s*|'|"]XD[\s*|'|"]:===:LOL
regex;[\s*|'|"]:-\)[\s*|'|"]:===:smile
regex;[\s*|'|"]:\)[\s*|'|"]:===:smile
regex;[\s*|'|"]->[\s*|'|"]:===:arrow right
# example for arrow left
#(?:[ |^])(<-)(?:[ ,.!?$]):===:arrow left
# or
#([ |^])<-([ ,.!?$]):===:arrow left\2
regex;[\s*|'|"]<-[\s*|'|"]:===:arrow left
regex;[\s+|'|"][O|o][O|o][\s+|'|"]:===:WTF?
regex;[\s*|'|"]\^\^[\s*|'|"]:===:enjoy smile
regex;(?:^|\s)(?::-\)|:\))(?:\s|$):===:smile
regex;(?:^|\s)(?::-\(|:\()(?:\s|$):===:frown
regex;(?:^|\s)(?::-D|:D)(?:\s|$):===:big grin
regex;(?:^|\s)(?:;-\)|;\))(?:\s|$):===:wink
regex;(?:^|\s)(?::-P|:P)(?:\s|$):===:tongue out
regex;(?:^|\s)(?::-O|:O)(?:\s|$):===:surprised
regex;(?:^|\s)(?::-/|:/)(?:\s|$):===:skeptical
regex;(?:^|\s)(?:>:-\(|>:\()(?:\s|$):===:angry
regex;(?:^|\s)(?::'\(|:'-\()(?:\s|$):===:crying
regex;(?:^|\s)(?::-\||:\|)(?:\s|$):===:neutral
regex;(?:^|\s)(?:8-\)|B-\))(?:\s|$):===:cool
regex;(?:^|\s)8-X(?:\s|$):===:skull and crossbones
regex;(?:^|\s)(?:X-X|x-x)(?:\s|$):===:knocked out
regex;(?:^|\s)(?::-X|:-x|:X|:x)(?:\s|$):===:lips sealed
regex;(?:^|\s)XD(?:\s|$):===:laughing hard
regex;(?:^|\s)\^_\^(?:\s|$):===:happy
regex;(?:^|\s)-_-(?:\s|$):===:annoyed
regex;(?:^|\s)o_O(?:\s|$):===:confused
regex;(?:^|\s)>_<(?:\s|$):===:frustrated
regex;(?:^|\s)\\o/(?:\s|$):===:hurray
regex;(?:^|\s)<3(?:\s|$):===:heart
regex;(?:^|\s)</3(?:\s|$):===:broken heart
regex;(?:^|\s)(?:Oo|oO)(?:\s|$):===:WTF?
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
+8 -11
View File
@@ -15,7 +15,7 @@ theme=default
# Sound volume controls how loud the sounds for your selected soundpack are.
# 0 is quietest, 1.0 is loudest.
volume=1.0
volume=0.7
# shell commands for generic sound driver
# the folowing variable are substituted
@@ -33,14 +33,11 @@ genericFrequencyCommand=play -q -v fenrirVolume -n -c1 synth fenrirDuration sine
enabled=True
# Select speech driver, options are speechdDriver or genericDriver:
#driver=speechdDriver
driver=genericDriver
# server path for emacspeak
# serverPath=
driver=speechdDriver
#driver=genericDriver
# The rate selects how fast Fenrir will speak. Options range from 0, slowest, to 1.0, fastest.
rate=0.65
rate=0.5
# Pitch controls the pitch of the voice, select from 0, lowest, to 1.0, highest.
pitch=0.5
@@ -95,8 +92,8 @@ fenrirMaxRate=450
driver=vcsaDriver
encoding=auto
screenUpdateDelay=0.05
suspendingScreen=
autodetectSuspendingScreen=True
ignoreScreen=
autodetectIgnoreScreen=True
[keyboard]
driver=evdevDriver
@@ -134,7 +131,7 @@ punctuationProfile=default
punctuationLevel=some
respectPunctuationPause=True
newLinePause=True
numberOfClipboards=10
numberOfClipboards=50
# used path for "export_clipboard_to_file"
# $user is replaced by username
#clipboardExportPath=/home/$user/fenrirClipboard
@@ -193,7 +190,7 @@ enableSettingsRemote=True
enableCommandRemote=True
[barrier]
enabled=True
enabled=False
leftBarriers=│└┌─
rightBarriers=│┘┐─
+5 -5
View File
@@ -1,12 +1,11 @@
#!/bin/bash
#!/usr/bin/env bash
#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
install -m755 -d /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
# tools
install -m755 -d /usr/share/fenrirscreenreader/tools
@@ -33,8 +32,9 @@ cp -af config/sound/template /usr/share/sounds/fenrirscreenreader/template
# config
if [ -f "/etc/fenrirscreenreader/settings/settings.conf" ]; then
echo "Do you want to overwrite your current global settings? (y/n)"
read yn
if [ $yn = "Y" -o $yn = "y" ]; then
read -r yn
yn="${yn:0:1}"
if [[ "${yn^}" == "Y" ]]; then
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."
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>=2.5.0
dbus-python>=1.2.8
pyudev>=0.21.0
daemonize
evdev
pexpect
pyttsx3
pyte>=0.7.0
pyenchant
pyperclip
pyte
pyudev
pyxdg
setproctitle
+37 -27
View File
@@ -1,21 +1,29 @@
#!/usr/bin/env python3
#https://python-packaging.readthedocs.io/en/latest/minimal.html
import os, glob, sys
import os.path
from shutil import copyfile
from setuptools import find_packages
from setuptools import find_namespace_packages
from setuptools import setup
fenrirVersion = '1.9.9'
packageVersion = 'post1'
# handle flags for package manager like aurman and pacaur.
forceSettings = False
# Allow both environment variable and command line flag
forceSettingsFlag = (
"--force-settings" in sys.argv or
os.environ.get('FENRIR_FORCE_SETTINGS') == '1'
)
if "--force-settings" in sys.argv:
forceSettings = True
sys.argv.remove("--force-settings")
data_files = []
dataFiles = []
# Handle locale files
localeFiles = glob.glob('locale/*/LC_MESSAGES/*.mo')
for localeFile in localeFiles:
lang = localeFile.split(os.sep)[1]
destDir = f'/usr/share/locale/{lang}/LC_MESSAGES'
dataFiles.append((destDir, [localeFile]))
# Handle other configuration files
directories = glob.glob('config/*')
for directory in directories:
files = glob.glob(directory+'/*')
@@ -26,25 +34,25 @@ for directory in directories:
destDir = '/etc/fenrirscreenreader/keyboard'
elif 'config/settings' in directory:
destDir = '/etc/fenrirscreenreader/settings'
if not forceSettings:
if not forceSettingsFlag:
try:
del(files[files.index('config/settings/settings.conf')])
files = [f for f in files if not f.endswith('settings.conf')]
except:
pass
elif 'config/scripts' in directory:
destDir = '/usr/share/fenrirscreenreader/scripts'
if destDir != '':
data_files.append((destDir, files))
dataFiles.append((destDir, files))
files = glob.glob('config/sound/default/*')
destDir = '/usr/share/sounds/fenrirscreenreader/default'
data_files.append((destDir, files))
dataFiles.append((destDir, files))
files = glob.glob('config/sound//template/*')
destDir = '/usr/share/sounds/fenrirscreenreader/template'
data_files.append((destDir, files))
dataFiles.append((destDir, files))
files = glob.glob('tools/*')
data_files.append(('/usr/share/fenrirscreenreader/tools', files))
data_files.append(('/usr/share/man/man1', ['docs/fenrir.1']))
dataFiles.append(('/usr/share/fenrirscreenreader/tools', files))
dataFiles.append(('/usr/share/man/man1', ['docs/fenrir.1']))
def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()
@@ -52,15 +60,13 @@ def read(fname):
setup(
# Application name:
name="fenrir-screenreader",
# Version number:
version=fenrirVersion + '.' + packageVersion,
# description
description="A TTY Screen Reader for Linux.",
long_description=read('README.md'),
long_description_content_type="text/markdown",
keywords=['screenreader', 'a11y', 'accessibility', 'terminal', 'TTY', 'console'],
license="License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
url="https://git.stormux.org/storm/fenrir/",
download_url = 'https://git.stormux.org/storm/fenrir/archive/' + fenrirVersion + '.tar.gz',
classifiers=[
"Programming Language :: Python",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
@@ -74,39 +80,43 @@ setup(
author_email="storm_dragon@stormux.org",
# Packages
packages=find_packages('src/'),
package_dir={'': 'src/'},
package_dir={'': 'src'},
packages=find_namespace_packages(
where='src',
include=['fenrirscreenreader*']
),
scripts=['src/fenrir'],
# Include additional files into the package
include_package_data=True,
zip_safe=False,
data_files=data_files,
data_files=dataFiles,
# Dependent packages (distributions)
python_requires='>=3.6',
install_requires=[
"evdev>=1.1.2",
"daemonize>=2.5.0",
"dbus-python>=1.2.8",
"pyperclip",
"pyudev>=0.21.0",
"setuptools",
"setproctitle",
"pexpect",
"pyttsx3",
"pyte>=0.7.0",
],
)
if not forceSettings:
if not forceSettingsFlag:
print('')
# create settings file from example if not exist
if not os.path.isfile('/etc/fenrirscreenreader/settings/settings.conf'):
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')
except:
pass
except OSError as e:
print(f"Could not copy settings file to destination: {e}")
else:
print('settings.conf file found. It is not overwritten automatical')
+1 -1
View File
@@ -98,7 +98,7 @@ def main():
argumentParser.error(errorMsg)
sys.exit(1)
if cliArgs.foreground:
if cliArgs.foreground or cliArgs.emulated_pty:
# Run directly in foreground
run_fenrir()
else:
@@ -5,15 +5,17 @@
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import subprocess, os
from subprocess import Popen, PIPE
import os
import importlib
import _thread
import pyperclip
class command():
def __init__(self):
pass
def initialize(self, environment):
def initialize(self, environment, scriptPath=''):
self.env = environment
self.scriptPath = scriptPath
def shutdown(self):
pass
def getDescription(self):
@@ -22,56 +24,48 @@ class command():
_thread.start_new_thread(self._threadRun , ())
def _threadRun(self):
try:
# Check if clipboard is empty
if self.env['runtime']['memoryManager'].isIndexListEmpty('clipboardHistory'):
self.env['runtime']['outputManager'].presentText(_('clipboard empty'), interrupt=True)
return
# Get current clipboard content
clipboard = self.env['runtime']['memoryManager'].getIndexListElement('clipboardHistory')
user = self.env['general']['currUser']
# First try to find xclip in common locations
xclip_paths = [
'/usr/bin/xclip',
'/bin/xclip',
'/usr/local/bin/xclip'
]
# Remember original display environment variable if it exists
originalDisplay = os.environ.get('DISPLAY', '')
success = False
xclip_path = None
for path in xclip_paths:
if os.path.isfile(path) and os.access(path, os.X_OK):
xclip_path = path
# Try different display options
for i in range(10):
display = f":{i}"
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
except Exception:
# Failed for this display, try next one
continue
if not xclip_path:
self.env['runtime']['outputManager'].presentText(
'xclip not found in common locations',
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)
# Restore original display setting
if originalDisplay:
os.environ['DISPLAY'] = originalDisplay
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:
self.env['runtime']['outputManager'].presentText(str(e), soundIcon='', interrupt=False)
def setCallback(self, callback):
pass
@@ -5,9 +5,11 @@
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import subprocess, os
from subprocess import Popen, PIPE
import importlib
import _thread
import pyperclip
import os
class command():
def __init__(self):
pass
@@ -22,33 +24,41 @@ class command():
_thread.start_new_thread(self._threadRun , ())
def _threadRun(self):
try:
# Find xclip path
xclip_paths = ['/usr/bin/xclip', '/bin/xclip', '/usr/local/bin/xclip']
xclip_path = None
for path in xclip_paths:
if os.path.isfile(path) and os.access(path, os.X_OK):
xclip_path = path
break
if not xclip_path:
self.env['runtime']['outputManager'].presentText('xclip not found in common locations', interrupt=True)
return
xClipboard = ''
for display in range(10):
p = Popen('su ' + self.env['general']['currUser'] + ' -p -c "' + xclip_path + ' -d :' + str(display) + ' -o"', stdout=PIPE, stderr=PIPE, shell=True)
stdout, stderr = p.communicate()
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)
# Remember original display environment variable if it exists
originalDisplay = os.environ.get('DISPLAY', '')
clipboardContent = None
# Try different display options
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
except Exception:
# Failed for this display, try next one
continue
# Restore original display setting
if originalDisplay:
os.environ['DISPLAY'] = originalDisplay
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(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:
self.env['runtime']['outputManager'].presentText(e , soundIcon='', interrupt=False)
self.env['runtime']['outputManager'].presentText(str(e), soundIcon='', interrupt=False)
def setCallback(self, callback):
pass
@@ -1,25 +0,0 @@
#!/bi[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]/py[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]ho[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]
# -*- [['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]odi[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]g: u[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]f-8 -*-
# F[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]i[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']] TTY [['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']] [['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]d[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]
# By Ch[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]y[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']], S[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]o[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]m [['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]go[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']], [['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]d [['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]o[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]ibu[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']].
f[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]om [['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]o[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']] impo[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']] d[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]bug
[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]l[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']] [['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]omm[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]d():
d[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]f __i[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]i[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]__([['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]lf):
p[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]
d[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]f i[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]i[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]i[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]liz[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]([['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]lf, [['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]vi[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]o[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]m[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]):
[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]lf.[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]v = [['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]vi[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]o[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]m[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]
d[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]f [['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]hu[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]dow[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]([['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]lf):
p[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]
d[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]f g[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]ip[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]io[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]([['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]lf):
[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]u[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']] [['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]No d[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]ip[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]io[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']] fou[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]d[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]
d[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]f [['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]u[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]([['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]lf):
#p[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]i[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]([['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]w [['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']], [['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]lf.[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]v[[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]][[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]wAppli[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]io[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]])
#p[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]i[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]([['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]old [['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']], [['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]lf.[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]v[[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]][[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]oldAppli[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]io[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]])
#p[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]i[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]([['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]-----------[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']])
p[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]
d[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]f [['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]C[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]llb[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]k([['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]lf, [['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]llb[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]k):
p[['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']][['screen']['screen']['screen']['screen']['screen']['screen']['screen']['screen']]
@@ -35,12 +35,18 @@ class command():
if not (self.env['runtime']['byteManager'].getLastByteKey() in [b'^[[A',b'^[[B']):
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']]
currLine = self.env['screen']['newContentText'].split('\n')[self.env['screen']['newCursor']['y']]
is_blank = currLine.strip() == ''
if prevLine == currLine:
if self.env['screen']['newDelta'] != '':
return
if not currLine.isspace():
announce = currLine
if not is_blank:
currPrompt = currLine.find('$')
rootPrompt = currLine.find('#')
if currPrompt <= 0:
@@ -55,13 +61,13 @@ class command():
else:
announce = currLine
if currLine.isspace():
if is_blank:
self.env['runtime']['outputManager'].presentText(_("blank"), soundIcon='EmptyLine', interrupt=True, flush=False)
else:
self.env['runtime']['outputManager'].presentText(announce, interrupt=True, flush=False)
self.env['commandsIgnore']['onScreenUpdate']['CHAR_DELETE_ECHO'] = True
self.env['commandsIgnore']['onScreenUpdate']['CHAR_ECHO'] = True
self.env['commandsIgnore']['onScreenUpdate']['INCOMING_IGNORE'] = True
def setCallback(self, callback):
pass
+11 -5
View File
@@ -159,18 +159,24 @@ class commandManager():
self.env['runtime']['debug'].writeDebugOut("Loading script:" + fileName ,debug.debugLevel.ERROR)
self.env['runtime']['debug'].writeDebugOut(str(e),debug.debugLevel.ERROR)
continue
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]):
try:
self.env['commands'][section][command].shutdown()
del self.env['commands'][section][command]
except Exception as e:
self.env['runtime']['debug'].writeDebugOut("Shutdown command:" + section + "." + command ,debug.debugLevel.ERROR)
self.env['runtime']['debug'].writeDebugOut(str(e),debug.debugLevel.ERROR)
self.env['runtime']['debug'].writeDebugOut("Shutdown command:" + section + "." + command, debug.debugLevel.ERROR)
self.env['runtime']['debug'].writeDebugOut(str(e), debug.debugLevel.ERROR)
continue
def executeSwitchTrigger(self, trigger, unLoadScript, loadScript):
if self.env['runtime']['screenManager'].isSuspendingScreen():
if self.env['runtime']['screenManager'].isIgnoredScreen():
return
#unload
oldScript = unLoadScript
@@ -193,7 +199,7 @@ class commandManager():
def executeDefaultTrigger(self, trigger, force=False):
if not force:
if self.env['runtime']['screenManager'].isSuspendingScreen():
if self.env['runtime']['screenManager'].isIgnoredScreen():
return
for command in sorted(self.env['commands'][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)
def executeCommand(self, command, section = 'commands'):
if self.env['runtime']['screenManager'].isSuspendingScreen():
if self.env['runtime']['screenManager'].isIgnoredScreen():
return
if self.commandExists(command, section):
try:
@@ -11,6 +11,14 @@ class cursorManager():
pass
def initialize(self, 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):
pass
def clearMarks(self):
+7 -4
View File
@@ -52,12 +52,15 @@ class debugManager():
else:
if not self._fileOpened and fileMode:
self.openDebugFile()
timestamp = str(datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f'))
if onAnyLevel:
msg = 'ANY '+ str(level) + ' ' + str(datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f'))
levelInfo = 'INFO ANY'
else:
msg = str(level) +' ' + str(datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')
)
msg += ': ' + text
levelInfo = str(level)
# Changed order: text comes first, then level and timestamp
msg = text + ' - ' + levelInfo + ' ' + timestamp
if printMode:
print(msg)
if fileMode:
+1 -1
View File
@@ -55,7 +55,7 @@ class fenrirManager():
if self.environment['runtime']['inputManager'].noKeyPressed():
self.environment['runtime']['inputManager'].clearLastDeepInput()
if self.environment['runtime']['screenManager'].isSuspendingScreen():
if self.environment['runtime']['screenManager'].isIgnoredScreen():
self.environment['runtime']['inputManager'].writeEventBuffer()
else:
if self.environment['runtime']['helpManager'].isTutorialMode():
@@ -42,6 +42,25 @@ class inputDriver():
if not self._initialized:
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):
if not self._initialized:
return False
+50 -6
View File
@@ -49,6 +49,7 @@ class inputManager():
return event
def setExecuteDeviceGrab(self, newExecuteDeviceGrab = True):
self.executeDeviceGrab = newExecuteDeviceGrab
def handleDeviceGrab(self, force = False):
if force:
self.setExecuteDeviceGrab()
@@ -61,17 +62,38 @@ class inputManager():
if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'):
self.executeDeviceGrab = False
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():
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)
self.env['runtime']['debug'].writeDebugOut("retry ungrabAllDevices " ,debug.debugLevel.WARNING)
self.env['runtime']['debug'].writeDebugOut("All devices ungrabbed" ,debug.debugLevel.INFO)
self.env['runtime']['debug'].writeDebugOut(f"retry ungrabAllDevices {retryCount}/{maxRetries}", debug.debugLevel.WARNING)
else:
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)
self.env['runtime']['debug'].writeDebugOut("retry grabAllDevices" ,debug.debugLevel.WARNING)
self.env['runtime']['debug'].writeDebugOut("All devices grabbed" ,debug.debugLevel.INFO)
self.env['runtime']['debug'].writeDebugOut(f"retry grabAllDevices {retryCount}/{maxRetries}", debug.debugLevel.WARNING)
self.executeDeviceGrab = False
def sendKeys(self, keyMacro):
for e in keyMacro:
key = ''
@@ -252,17 +274,39 @@ class inputManager():
def getCurrShortcut(self, inputSequence = None):
shortcut = []
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:
# 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)
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'])
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 = []
self.env['input']['shortcutRepeat'] = 1
shortcut.append(self.env['input']['shortcutRepeat'])
shortcut.append(self.env['input']['currInput'])
self.env['runtime']['debug'].writeDebugOut("currShortcut " + str(shortcut) ,debug.debugLevel.INFO)
self.env['runtime']['debug'].writeDebugOut("currShortcut " + str(shortcut), debug.debugLevel.INFO)
return str(shortcut)
def currKeyIsModifier(self):
+6 -6
View File
@@ -59,7 +59,7 @@ class screenManager():
if self.isCurrScreenIgnoredChanged():
self.env['runtime']['inputManager'].setExecuteDeviceGrab()
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.env['screen']['lastScreenUpdate'] = time.time()
else:
@@ -81,7 +81,7 @@ class screenManager():
return self.prevScreenIgnored
def updateScreenIgnored(self):
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'):
# set new "old" values
self.env['screen']['oldContentBytes'] = self.env['screen']['newContentBytes']
@@ -174,16 +174,16 @@ class screenManager():
except Exception as e:
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:
screen = self.env['screen']['newTTY']
ignoreScreens = []
fixIgnoreScreens = self.env['runtime']['settingsManager'].getSetting('screen', 'suspendingScreen')
fixIgnoreScreens = self.env['runtime']['settingsManager'].getSetting('screen', 'ignoreScreen')
if fixIgnoreScreens != '':
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'])
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)
def isScreenChange(self):
+2 -2
View File
@@ -40,8 +40,8 @@ settingsData = {
'driver': 'vcsaDriver',
'encoding': 'auto',
'screenUpdateDelay': 0.1,
'suspendingScreen': '',
'autodetectSuspendingScreen': False,
'ignoreScreen': '',
'autodetectIgnoreScreen': False,
},
'general':{
'debugLevel': debug.debugLevel.DEACTIVE,
+2 -2
View File
@@ -4,5 +4,5 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
version = "2024.12.10"
codeName = "stable"
version = "2025.04.28"
codeName = "master"
@@ -59,7 +59,7 @@ class driver(inputDriver):
self.env['runtime']['processManager'].addCustomEventThread(self.inputWatchdog)
self._initialized = True
def plugInputDeviceWatchdogUdev(self,active , eventQueue):
def plugInputDeviceWatchdogUdev(self, active, eventQueue):
context = pyudev.Context()
monitor = pyudev.Monitor.from_netlink(context)
monitor.filter_by(subsystem='input')
@@ -72,31 +72,33 @@ class driver(inputDriver):
self.env['runtime']['debug'].writeDebugOut('plugInputDeviceWatchdogUdev:' + str(device), debug.debugLevel.INFO)
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
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
if 'BRLTTY' in device.name.upper():
if hasattr(device, 'name') and device.name and 'BRLTTY' in device.name.upper():
ignorePlug = True
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:
virtual = '/sys/devices/virtual/input/' in device.sys_path
if device.device_node:
validDevices.append({'device': device.device_node, 'virtual': virtual})
except Exception as e:
self.env['runtime']['debug'].writeDebugOut("plugInputDeviceWatchdogUdev APPEND CRASH: " + str(e),debug.debugLevel.ERROR)
self.env['runtime']['debug'].writeDebugOut("plugInputDeviceWatchdogUdev APPEND CRASH: " + str(e), debug.debugLevel.ERROR)
try:
pollTimeout = 1
device = monitor.poll(pollTimeout)
except:
except Exception:
device = None
ignorePlug = False
if validDevices:
eventQueue.put({"Type":fenrirEventType.PlugInputDevice,"Data":validDevices})
eventQueue.put({"Type": fenrirEventType.PlugInputDevice, "Data": validDevices})
return time.time()
def inputWatchdog(self,active , eventQueue):
def inputWatchdog(self, active, eventQueue):
try:
while active.value:
r, w, x = select(self.iDevices, [], [], 0.8)
@@ -111,7 +113,7 @@ class driver(inputDriver):
self.removeDevice(fd)
while(event):
self.env['runtime']['debug'].writeDebugOut('inputWatchdog: EVENT:' + str(event), debug.debugLevel.INFO)
self.env['input']['eventBuffer'].append( [self.iDevices[fd], self.uDevices[fd], event])
self.env['input']['eventBuffer'].append([self.iDevices[fd], self.uDevices[fd], event])
if event.type == evdev.events.EV_KEY:
if not foundKeyInSequence:
foundKeyInSequence = True
@@ -123,11 +125,11 @@ class driver(inputDriver):
if not isinstance(currMapEvent['EventName'], str):
event = self.iDevices[fd].read_one()
continue
if currMapEvent['EventState'] in [0,1,2]:
eventQueue.put({"Type":fenrirEventType.KeyboardInput,"Data":currMapEvent.copy()})
if currMapEvent['EventState'] in [0, 1, 2]:
eventQueue.put({"Type": fenrirEventType.KeyboardInput, "Data": currMapEvent.copy()})
eventFired = True
else:
if event.type in [2,3]:
if event.type in [2, 3]:
foreward = True
event = self.iDevices[fd].read_one()
@@ -136,7 +138,7 @@ class driver(inputDriver):
self.writeEventBuffer()
self.clearEventBuffer()
except Exception as e:
self.env['runtime']['debug'].writeDebugOut("INPUT WATCHDOG CRASH: "+str(e),debug.debugLevel.ERROR)
self.env['runtime']['debug'].writeDebugOut("INPUT WATCHDOG CRASH: " + str(e), debug.debugLevel.ERROR)
def writeEventBuffer(self):
if not self._initialized:
@@ -146,7 +148,7 @@ class driver(inputDriver):
if uDevice:
if self.gDevices[iDevice.fd]:
self.writeUInput(uDevice, event)
except Exception as e:
except Exception:
pass
def writeUInput(self, uDevice, event):
@@ -156,7 +158,7 @@ class driver(inputDriver):
time.sleep(0.0000002)
uDevice.syn()
def updateInputDevices(self, newDevices = None, init = False):
def updateInputDevices(self, newDevices=None, init=False):
if init:
self.removeAllDevices()
@@ -191,7 +193,7 @@ class driver(inputDriver):
try:
with open(deviceFile) as f:
pass
except Exception as e:
except Exception:
continue
# 3 pos absolute
# 2 pos relative
@@ -201,22 +203,23 @@ class driver(inputDriver):
except:
continue
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
if currDevice.phys.upper() in ['','SPEAKUP','FENRIR-UINPUT']:
if hasattr(currDevice, 'phys') and currDevice.phys and currDevice.phys.upper() in ['', 'SPEAKUP', 'FENRIR-UINPUT']:
continue
if 'BRLTTY' in currDevice.name.upper():
if hasattr(currDevice, 'name') and currDevice.name and 'BRLTTY' in currDevice.name.upper():
continue
except:
pass
cap = currDevice.capabilities()
if mode in ['ALL','NOMICE']:
if mode in ['ALL', 'NOMICE']:
if eventType.EV_KEY in cap:
if 116 in cap[eventType.EV_KEY] and len(cap[eventType.EV_KEY]) < 10:
self.env['runtime']['debug'].writeDebugOut('Device Skipped (has 116):' + currDevice.name,debug.debugLevel.INFO)
self.env['runtime']['debug'].writeDebugOut('Device Skipped (has 116):' + currDevice.name, debug.debugLevel.INFO)
continue
if len(cap[eventType.EV_KEY]) < 60:
self.env['runtime']['debug'].writeDebugOut('Device Skipped (< 60 keys):' + currDevice.name,debug.debugLevel.INFO)
self.env['runtime']['debug'].writeDebugOut('Device Skipped (< 60 keys):' + currDevice.name, debug.debugLevel.INFO)
continue
if mode == 'ALL':
self.addDevice(currDevice)
@@ -224,16 +227,20 @@ class driver(inputDriver):
elif mode == 'NOMICE':
if not ((eventType.EV_REL in cap) or (eventType.EV_ABS in cap)):
self.addDevice(currDevice)
self.env['runtime']['debug'].writeDebugOut('Device added (NOMICE):' + self.iDevices[currDevice.fd].name,debug.debugLevel.INFO)
self.env['runtime']['debug'].writeDebugOut('Device added (NOMICE):' + self.iDevices[currDevice.fd].name, debug.debugLevel.INFO)
else:
self.env['runtime']['debug'].writeDebugOut('Device Skipped (NOMICE):' + currDevice.name,debug.debugLevel.INFO)
self.env['runtime']['debug'].writeDebugOut('Device Skipped (NOMICE):' + currDevice.name, debug.debugLevel.INFO)
else:
self.env['runtime']['debug'].writeDebugOut('Device Skipped (no EV_KEY):' + currDevice.name,debug.debugLevel.INFO)
self.env['runtime']['debug'].writeDebugOut('Device Skipped (no EV_KEY):' + currDevice.name, debug.debugLevel.INFO)
elif currDevice.name.upper() in mode.split(','):
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:
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.updateMPiDevicesFD()
@@ -247,6 +254,7 @@ class driver(inputDriver):
self.iDevicesFD.remove(fd)
except:
pass
def mapEvent(self, event):
if not self._initialized:
return None
@@ -266,12 +274,12 @@ class driver(inputDriver):
mEvent['EventSec'] = event.sec
mEvent['EventUsec'] = event.usec
mEvent['EventState'] = event.value
mEvent['EventType'] = event.type
mEvent['EventType'] = event.type
return mEvent
except Exception as e:
except Exception:
return None
def getLedState(self, led = 0):
def getLedState(self, led=0):
if not self.hasIDevices():
return False
# 0 = Numlock
@@ -281,7 +289,8 @@ class driver(inputDriver):
if led in dev.leds():
return True
return False
def toggleLedState(self, led = 0):
def toggleLedState(self, led=0):
if not self.hasIDevices():
return False
ledState = self.getLedState(led)
@@ -290,9 +299,10 @@ class driver(inputDriver):
# 17 LEDs
if 17 in self.iDevices[i].capabilities():
if ledState == 1:
self.iDevices[i].set_led(led , 0)
self.iDevices[i].set_led(led, 0)
else:
self.iDevices[i].set_led(led , 1)
self.iDevices[i].set_led(led, 1)
def grabAllDevices(self):
if not self._initialized:
return True
@@ -301,6 +311,7 @@ class driver(inputDriver):
if not self.gDevices[fd]:
ok = ok and self.grabDevice(fd)
return ok
def ungrabAllDevices(self):
if not self._initialized:
return True
@@ -309,6 +320,7 @@ class driver(inputDriver):
if self.gDevices[fd]:
ok = ok and self.ungrabDevice(fd)
return ok
def createUInputDev(self, fd):
if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'):
self.uDevices[fd] = None
@@ -324,20 +336,21 @@ class driver(inputDriver):
self.uDevices[fd] = UInput.from_device(self.iDevices[fd], name='fenrir-uinput', phys='fenrir-uinput')
except Exception as e:
try:
self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: compat fallback: ' + str(e),debug.debugLevel.WARNING)
self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: compat fallback: ' + str(e), debug.debugLevel.WARNING)
dev = self.iDevices[fd]
cap = dev.capabilities()
del cap[0]
self.uDevices[fd] = UInput(
cap,
name = 'fenrir-uinput',
phys = 'fenrir-uinput'
name='fenrir-uinput',
phys='fenrir-uinput'
)
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
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:
self.iDevices[newDevice.fd] = newDevice
self.createUInputDev(newDevice.fd)
@@ -360,10 +373,13 @@ class driver(inputDriver):
def grabDevice(self, fd):
if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'):
return True
# FIX: Handle exception variable scope correctly
grab_error = None
try:
self.iDevices[fd].grab()
self.gDevices[fd] = True
self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: grab device ('+ str(self.iDevices[fd].name) + ')',debug.debugLevel.INFO)
self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: grab device (' + str(self.iDevices[fd].name) + ')', debug.debugLevel.INFO)
# Reset modifier keys on successful grab
if self.uDevices[fd]:
modifierKeys = [e.KEY_LEFTCTRL, e.KEY_RIGHTCTRL, e.KEY_LEFTALT, e.KEY_RIGHTALT, e.KEY_LEFTSHIFT, e.KEY_RIGHTSHIFT]
@@ -371,33 +387,44 @@ class driver(inputDriver):
try:
self.uDevices[fd].write(e.EV_KEY, key, 0) # 0 = key up
self.uDevices[fd].syn()
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('Failed to reset modifier key: ' + str(e), debug.debugLevel.WARNING)
except Exception as mod_error:
self.env['runtime']['debug'].writeDebugOut('Failed to reset modifier key: ' + str(mod_error), debug.debugLevel.WARNING)
except IOError:
if not self.gDevices[fd]:
return False
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: grabing not possible: ' + str(e),debug.debugLevel.ERROR)
except Exception as ex:
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 True
def ungrabDevice(self,fd):
def ungrabDevice(self, fd):
if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'):
return True
# FIX: Handle exception variable scope correctly
ungrab_error = None
try:
self.iDevices[fd].ungrab()
self.gDevices[fd] = False
self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: ungrab device ('+ str(self.iDevices[fd].name) + ')',debug.debugLevel.INFO)
self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: ungrab device (' + str(self.iDevices[fd].name) + ')', debug.debugLevel.INFO)
except IOError:
if self.gDevices[fd]:
return False
# self.gDevices[fd] = False
# #self.removeDevice(fd)
except Exception as e:
except Exception as ex:
ungrab_error = ex
if ungrab_error:
self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: ungrabing not possible: ' + str(ungrab_error), debug.debugLevel.ERROR)
return False
return True
def removeDevice(self,fd):
self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: device removed: ' + str(fd) + ' ' +str(self.iDevices[fd]),debug.debugLevel.INFO)
def removeDevice(self, fd):
self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: device removed: ' + str(fd) + ' ' + str(self.iDevices[fd]), debug.debugLevel.INFO)
self.clearEventBuffer()
try:
self.ungrabDevice(fd)
@@ -1,70 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# generic driver
import pexpect
import shlex
import time
from fenrirscreenreader.core import debug
from fenrirscreenreader.core.speechDriver import speechDriver
class driver(speechDriver):
def __init__(self):
speechDriver.__init__(self)
def initialize(self, environment):
self.env = environment
try:
self.server = pexpect.spawn('tclsh ' + self.env['runtime']['settingsManager'].getSetting('speech', 'serverPath'))
self._isInitialized = True
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('speechDriver:initialize:' + str(e),debug.debugLevel.ERROR)
def shutdown(self):
if self.server:
try:
self.server.terminate()
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('speechDriver:shutdown:self.server.terminate():' + str(e),debug.debugLevel.ERROR)
def speak(self,text, queueable=True):
if not self._isInitialized:
return
if not queueable:
self.cancel()
try:
cleanText = text
for c in '[]{}\\|_@#^*<>\"`~^':
cleanText.replace(c,'')
cleanText = shlex.split(cleanText)
for idx, word in enumerate(cleanText):
cleanText[idx] = word
cleanText = ' '.join(cleanText)
cleanText = 'tts_say \"' + cleanText +'\"'
self.server.sendline(cleanText)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('speechDriver:speak:self.server.sendline():' + str(e),debug.debugLevel.ERROR)
def cancel(self):
if not self._isInitialized:
return
try:
self.server.sendline('stop')
except Exception as e:
print(e)
self.env['runtime']['debug'].writeDebugOut('speechDriver:cancel:self.server.sendline():' + str(e),debug.debugLevel.ERROR)
def setRate(self, rate):
if not self._isInitialized:
return
try:
self.server.sendline('tts_set_speech_rate ' + str(int(rate * 400)))
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('speechDriver:setRate:self.server.sendline():' + str(e),debug.debugLevel.ERROR)
def setLanguage(self, language):
if not self._isInitialized:
return
self.server.sendline('set_lang ' + language)
@@ -1,85 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# Espeak driver
from threading import Thread, Lock
from fenrirscreenreader.core import debug
from fenrirscreenreader.core.speechDriver import speechDriver
class driver(speechDriver):
def __init__(self):
speechDriver.__init__(self)
self._engine = None
def initialize(self, environment):
self.env = environment
def shutdown(self):
if self._isInitialized:
self.cancel()
self._engine.endLoop()
self._initialized = False
def eventLoop(self):
self._engine.startLoop()
def startEngine(self):
try:
import pyttsx3
if self.module != '':
self._engine = pyttsx3.init(self.module)
else:
self._engine = pyttsx3.init()
self.eventLoopThread = Thread(target=self.eventLoop)
self._isInitialized = True
self.eventLoopThread.start()
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('SpeechDriver:initialize:' + str(e),debug.debugLevel.ERROR)
def speak(self,text, interrupt=True):
if not self._isInitialized:
self.startEngine()
if not self._isInitialized:
return
if not interrupt:
self.cancel()
try:
self._engine.setProperty('volume', self.volume)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('SpeechDriver:speak:volume:' + str(e),debug.debugLevel.ERROR)
try:
self._engine.setProperty('rate', self.rate)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('SpeechDriver:speak:rate:' + str(e),debug.debugLevel.ERROR)
try:
self._engine.setProperty('pitch', self.pitch)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('SpeechDriver:speak:pitch:' + str(e),debug.debugLevel.ERROR)
if self.language != None:
if self.language != '':
try:
self._engine.setProperty('voice', self.language)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('SpeechDriver:speak:language:' + str(e),debug.debugLevel.ERROR)
elif self.voice != None:
if self.voice != '':
try:
self._engine.setProperty('voice', self.voice)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('SpeechDriver:speak:voice:' + str(e),debug.debugLevel.ERROR)
self._engine.say(text)
def cancel(self):
if not self._isInitialized:
return
self._engine.stop()
def setPitch(self, pitch):
if not self._isInitialized:
return
self.pitch = pitch
def setRate(self, rate):
if not self._isInitialized:
return
self.rate = rate
+2 -2
View File
@@ -201,7 +201,7 @@ class FenrirConfigTool:
# Test voice configuration
if self.test_voice(moduleChoice, voice):
config = configparser.ConfigParser()
config = configparser.ConfigParser(interpolation=None)
config.read(self.settingsFile)
if 'speech' not in config:
@@ -244,7 +244,7 @@ class FenrirConfigTool:
def edit_general_config(self) -> None:
while True:
config = configparser.ConfigParser()
config = configparser.ConfigParser(interpolation=None)
config.read(self.settingsFile)
sections = config.sections()