Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Making the fonts optional #55

Open
fmarier opened this issue Dec 27, 2022 · 30 comments
Open

Making the fonts optional #55

fmarier opened this issue Dec 27, 2022 · 30 comments
Assignees
Labels

Comments

@fmarier
Copy link
Contributor

fmarier commented Dec 27, 2022

Right now, it looks like the fonts are required for the UI to load. It would be great if the app would fallback to using whatever the defaults fonts are on the system when the Roboto* files are missing.

Some dyslexic users for example choose to set their system fonts to https://opendyslexic.org/ and therefore it would be better on some systems to avoid overriding the user-selected fonts. (I would probably do this on Debian.)

@maltfield
Copy link
Member

maltfield commented Dec 27, 2022

Thanks for the ticket :)

Just to clarify: Currently, if the font file is missing, no font is rendered at-all? It doesn't currently fallback to system fonts? If fallback is broken, that's terrible. I would expect that kivy would already handle this..

I know you build BusKill different from me. Could you please post the steps to reproduce this?

I certainly plan to add the ability for a user to customize the font face and size after #16 unblocks #37.

But I'm on the fence if I want the BusKill app to actually default to the system-defined font. I guess I would be open to this if I could programmatically determine if the system font is set to either [a] the OS default or [b] if the user explicitly customized the system font. If the system font was explicitly set by the user, then I agree it should default to the system font (eg for accessibility reasons).

@fmarier
Copy link
Contributor Author

fmarier commented Dec 31, 2022

Currently if the font is not in the expected directory (fonts/ from where buskill_gui.py lives), then a fatal exception is thrown and the application exits:

 Traceback (most recent call last):
   File "/usr/share/buskill/main.py", line 139, in <module>
     from buskill_gui import BusKillApp
   File "/usr/share/buskill/buskill_gui.py", line 533, in <module>
     class BusKillApp(App):
   File "/usr/share/buskill/buskill_gui.py", line 553, in BusKillApp
     LabelBase.register(
   File "/usr/lib/python3/dist-packages/kivy/core/text/__init__.py", line 315, in register
     raise IOError('File {0} not found'.format(font_type))
 OSError: File fonts/RobotoMono-Regular.ttf not found

even if the font is available system-wide.

@fmarier
Copy link
Contributor Author

fmarier commented Dec 31, 2022

If you want to replicate the setup I have:

  1. Copy src/* into /usr/share/buskill/ recursively.
  2. Create /usr/bin/buskill which contains the following:
#!/bin/bash
exec "/usr/bin/python3" "/usr/share/buskill/main.py" "$@"
exit "$?"

Then, you can delete the /usr/share/buskill/fonts/RobotoMono-Regular.tff file and you'll get the above error.

@maltfield
Copy link
Member

maltfield commented Mar 16, 2024

I can reproduce the issue where the app doesn't load when the fonts file cannot be found.

However:

  1. I see no cross-platform way to get the "system default font" in Python (even across all Linux DEs), and
  2. Kivy actually ships with Roboto by default

On a fresh install of buskill on Debian 12, for example, we see can call kivy.core.text.LabelBase.get_system_fonts_dir(), and we get

['/usr/share/fonts', '/usr/share/fonts/cMap', '/usr/share/fonts/cmap', '/usr/share/fonts/woff', '/usr/share/fonts/woff/material-design-icons-iconfont', '/usr/share/fonts/woff/ebgaramond', '/usr/share/fonts/fonts-go', '/usr/share/fonts/type1', '/usr/share/fonts/type1/texlive-fonts-recommended', '/usr/share/fonts/type1/urw-base35', '/usr/share/fonts/eot', '/usr/share/fonts/eot/material-design-icons-iconfont', '/usr/share/fonts/X11', '/usr/share/fonts/X11/75dpi', '/usr/share/fonts/X11/encodings', '/usr/share/fonts/X11/encodings/large', '/usr/share/fonts/X11/Type1', '/usr/share/fonts/X11/util', '/usr/share/fonts/X11/100dpi', '/usr/share/fonts/X11/misc', '/usr/share/fonts/truetype', '/usr/share/fonts/truetype/lato', '/usr/share/fonts/truetype/andika', '/usr/share/fonts/truetype/croscore', '/usr/share/fonts/truetype/material-design-icons-iconfont', '/usr/share/fonts/truetype/font-awesome', '/usr/share/fonts/truetype/noto', '/usr/share/fonts/truetype/libreoffice', '/usr/share/fonts/truetype/freefont', '/usr/share/fonts/truetype/clear-sans', '/usr/share/fonts/truetype/charis', '/usr/share/fonts/truetype/crosextra', '/usr/share/fonts/truetype/gentiumplus', '/usr/share/fonts/truetype/droid', '/usr/share/fonts/truetype/gentiumplus-compact', '/usr/share/fonts/truetype/liberation2', '/usr/share/fonts/truetype/adf', '/usr/share/fonts/truetype/roboto', '/usr/share/fonts/truetype/roboto/unhinted', '/usr/share/fonts/truetype/roboto/unhinted/RobotoTTF', '/usr/share/fonts/truetype/liberation', '/usr/share/fonts/truetype/comfortaa', '/usr/share/fonts/truetype/paratype', '/usr/share/fonts/truetype/lyx', '/usr/share/fonts/truetype/open-sans', '/usr/share/fonts/truetype/dejavu', '/usr/share/fonts/truetype/gentium-basic', '/usr/share/fonts/truetype/quicksand', '/usr/share/fonts/truetype/ebgaramond', '/usr/share/fonts/truetype/gentium', '/usr/share/fonts/truetype/ancient-scripts', '/usr/share/fonts/opentype', '/usr/share/fonts/opentype/artemisia', '/usr/share/fonts/opentype/inter', '/usr/share/fonts/opentype/stix-word', '/usr/share/fonts/opentype/font-awesome', '/usr/share/fonts/opentype/stix', '/usr/share/fonts/opentype/freefont', '/usr/share/fonts/opentype/didot', '/usr/share/fonts/opentype/solomos', '/usr/share/fonts/opentype/junicode', '/usr/share/fonts/opentype/complutum', '/usr/share/fonts/opentype/linux-libertine', '/usr/share/fonts/opentype/asana-math', '/usr/share/fonts/opentype/lobstertwo', '/usr/share/fonts/opentype/cantarell', '/usr/share/fonts/opentype/lobster', '/usr/share/fonts/opentype/roboto', '/usr/share/fonts/opentype/roboto/slab', '/usr/share/fonts/opentype/olga', '/usr/share/fonts/opentype/comic-neue', '/usr/share/fonts/opentype/urw-base35', '/usr/share/fonts/opentype/ebgaramond', '/usr/share/fonts/opentype/neohellenic', '/usr/share/fonts/opentype/cabin', '/usr/local/share/fonts', '/usr/lib/python3/dist-packages/kivy/data/fonts']

The Roboto font is actually installed 3 times by the following packages:

  1. buskill
  2. python3-kivy
  3. fonts-roboto-unhinted
user@disp9272:/usr$ dpkg -S /usr/share/buskill/fonts/Roboto-Regular.ttf 
buskill: /usr/share/buskill/fonts/Roboto-Regular.ttf
user@disp9272:/usr$ 

user@disp9272:/usr$ dpkg -S /usr/lib/python3/dist-packages/kivy/data/fonts/Roboto-Regular.ttf 
python3-kivy: /usr/lib/python3/dist-packages/kivy/data/fonts/Roboto-Regular.ttf
user@disp9272:/usr$ 

user@disp9272:/usr$ dpkg -S /usr/share/fonts/truetype/roboto/unhinted/RobotoTTF/Roboto-Regular.ttf
fonts-roboto-unhinted: /usr/share/fonts/truetype/roboto/unhinted/RobotoTTF/Roboto-Regular.ttf
user@disp9272:/usr$ 

So @fmarier you can probably remove the fonts-roboto dependency on the buskill package, since it's already installed by python3-kivy. And, well, it's also being installed by this buskill package too

user@disp9272:/usr$ ls /usr/lib/python3/dist-packages/kivy/data/fonts/
DejaVuSans.ttf         Roboto-Bold.ttf    RobotoMono-Regular.ttf
Roboto-BoldItalic.ttf  Roboto-Italic.ttf  Roboto-Regular.ttf
user@disp9272:/usr$ 

user@disp9272:/usr$ ls /usr/share/buskill/fonts/
MaterialIcons-Regular.ttf  RobotoMono-Regular.ttf
Roboto-Medium.ttf          Roboto-Regular.ttf
user@disp9272:/usr$ 

@maltfield
Copy link
Member

maltfield commented Mar 16, 2024

As for fallbacks to system fonts, I don't think that's possible in python/kivy.

The best I could do is find some random font in the fonts dir returned by kivy's LabelBase.get_system_fonts_dir(), but I would have no way of knowing if the user, for example, had set some dyslexic-friendly font as their default system font

The default font in kivy appears to be, in fact, Roboto

user@disp9272:/usr$ ls /usr/lib/python3/dist-packages/kivy/data/fonts/
DejaVuSans.ttf         Roboto-Bold.ttf    RobotoMono-Regular.ttf
Roboto-BoldItalic.ttf  Roboto-Italic.ttf  Roboto-Regular.ttf
user@disp9272:/usr$ 


user@disp9272:/usr$ dpkg -S /usr/lib/python3/dist-packages/kivy/data/fonts/Roboto-Regular.ttf 
python3-kivy: /usr/lib/python3/dist-packages/kivy/data/fonts/Roboto-Regular.ttf
user@disp9272:/usr$ 
# msg = "DEBUG: Default font = " + str(Config.get('kivy', 'default_font'))
# print( msg ); logger.debug( msg )
DEBUG: Default font = ['Roboto', 'data/fonts/Roboto-Regular.ttf', 'data/fonts/Roboto-Italic.ttf', 'data/fonts/Roboto-Bold.ttf', 'data/fonts/Roboto-BoldItalic.ttf']

The one thing that I'm doing that diverges is that I've used Roboto-Medium.ttf in a few cases, which actually does not ship with python3-kivy

@maltfield
Copy link
Member

TODO: make this more robust by

  1. Renaming "Roboto", "RobotoMedium", and "RobotoMono" to "FontRegular", "FontMedium", and "FontMono"
  2. If the first attempt to load the fonts in buskill_gui.py fails, then try to "find" Roboto-Medium.ttf and the other font files in all the system font dirs

@fmarier
Copy link
Contributor Author

fmarier commented Mar 17, 2024

you can probably remove the fonts-roboto dependency on the buskill package, since it's already installed by python3-kivy. And, well, it's also being installed by this buskill package too

Both of these Roboto-Regular.ttf are symlinks to the font shipped by the fonts-roboto package:

$ ls -l /usr/lib/python3/dist-packages/kivy/data/fonts/Roboto-Regular.ttf
lrwxrwxrwx - root root 10 déc  2023 /usr/lib/python3/dist-packages/kivy/data/fonts/Roboto-Regular.ttf -> ../../../../../../share/fonts/truetype/roboto/unhinted/RobotoTTF/Roboto-Regular.ttf
$ ls -l /usr/share/buskill/fonts/Roboto-Regular.ttf 
lrwxrwxrwx - root root  8 sep  2023 /usr/share/buskill/fonts/Roboto-Regular.ttf -> ../../fonts/truetype/roboto/unhinted/RobotoTTF/Roboto-Regular.ttf

So there's only one copy of it on the system.

@fmarier
Copy link
Contributor Author

fmarier commented Mar 17, 2024

The one exception is RobotoMono-Regular.ttf which until recently wasn't explicitly Open Source and hasn't yet been added to Debian (now that its licensing status has been clarified): https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=819273#99

@maltfield
Copy link
Member

I spent some time looking for how a user could specify their own custom font (eg for accessibility needs), but it's non-trivial.

There is no built-in "font picker" in kivy. I didn't find any examples from others online. The best I've got is a list of directories with font files, and then I can recursively poke through those for ttf files.

I created a ComplexOption in the Settings screen for "font" and wrote some code to dynamically find all .ttf files and add them as an option on the screen. The result is horrendous. A few iterations of tinkering slowed my computer down. Then it totally locked-up my computer and I had to restart the VM.

Most of these fonts are probably useless anyway. For example, I randomly picked-one and it was a font for the Tamil language. We need a way to par-down the list to be shorter.

TODO

  1. remove symlinks (duplicates) from the list of fonts
  2. remove fonts that don't have glyphs for latin characters

For #2, I found some code on SE that uses the python module fonttools, but I actually liked this one better -- as it just uses the built-in module unicodedata

maltfield added a commit that referenced this issue Mar 17, 2024
Good news: I did succed in being able to populate the ComplexOption with a list of OptionItems for every font found on the system.

Bad news: It crashed my system.

Most of these fonts are probably useless anyway. For example, I randomly picked-one and it was a font for the Tamil language. We need a way to par-down the list to be shorter.

TODO:
 1. remove symlinks (duplicates) from the list of fonts
 2. remove fonts that don't have glyphs for latin characters

For #2, I found some code on SE that uses the python module fonttools, but I actually liked this one better -- as it just uses the built-in module unicodedata

 * https://superuser.com/questions/876572/how-do-i-find-out-which-font-contains-a-certain-special-character

For more info, see:

 * #55 (comment)
@maltfield
Copy link
Member

maltfield commented Mar 17, 2024

I found that, on my dev system, I have:

  1. 77 font directories containing
  2. 6,182 font files
user@buskill:~/tmp/fonts$ cat font_test.py 
#!/usr/bin/env python3
import os
font_dirs = ['/usr/share/fonts', '/usr/share/fonts/cMap', '/usr/share/fonts/cmap', '/usr/share/fonts/woff', '/usr/share/fonts/woff/material-design-icons-iconfont', '/usr/share/fonts/woff/ebgaramond', '/usr/share/fonts/fonts-go', '/usr/share/fonts/type1', '/usr/share/fonts/type1/texlive-fonts-recommended', '/usr/share/fonts/type1/urw-base35', '/usr/share/fonts/eot', '/usr/share/fonts/eot/material-design-icons-iconfont', '/usr/share/fonts/X11', '/usr/share/fonts/X11/75dpi', '/usr/share/fonts/X11/encodings', '/usr/share/fonts/X11/encodings/large', '/usr/share/fonts/X11/Type1', '/usr/share/fonts/X11/util', '/usr/share/fonts/X11/100dpi', '/usr/share/fonts/X11/misc', '/usr/share/fonts/truetype', '/usr/share/fonts/truetype/lato', '/usr/share/fonts/truetype/andika', '/usr/share/fonts/truetype/croscore', '/usr/share/fonts/truetype/material-design-icons-iconfont', '/usr/share/fonts/truetype/font-awesome', '/usr/share/fonts/truetype/noto', '/usr/share/fonts/truetype/libreoffice', '/usr/share/fonts/truetype/freefont', '/usr/share/fonts/truetype/clear-sans', '/usr/share/fonts/truetype/charis', '/usr/share/fonts/truetype/crosextra', '/usr/share/fonts/truetype/gentiumplus', '/usr/share/fonts/truetype/droid', '/usr/share/fonts/truetype/gentiumplus-compact', '/usr/share/fonts/truetype/liberation2', '/usr/share/fonts/truetype/adf', '/usr/share/fonts/truetype/roboto', '/usr/share/fonts/truetype/roboto/unhinted', '/usr/share/fonts/truetype/roboto/unhinted/RobotoTTF', '/usr/share/fonts/truetype/liberation', '/usr/share/fonts/truetype/comfortaa', '/usr/share/fonts/truetype/paratype', '/usr/share/fonts/truetype/lyx', '/usr/share/fonts/truetype/open-sans', '/usr/share/fonts/truetype/dejavu', '/usr/share/fonts/truetype/gentium-basic', '/usr/share/fonts/truetype/quicksand', '/usr/share/fonts/truetype/ebgaramond', '/usr/share/fonts/truetype/gentium', '/usr/share/fonts/truetype/ancient-scripts', '/usr/share/fonts/opentype', '/usr/share/fonts/opentype/artemisia', '/usr/share/fonts/opentype/inter', '/usr/share/fonts/opentype/stix-word', '/usr/share/fonts/opentype/font-awesome', '/usr/share/fonts/opentype/stix', '/usr/share/fonts/opentype/freefont', '/usr/share/fonts/opentype/didot', '/usr/share/fonts/opentype/solomos', '/usr/share/fonts/opentype/junicode', '/usr/share/fonts/opentype/complutum', '/usr/share/fonts/opentype/linux-libertine', '/usr/share/fonts/opentype/asana-math', '/usr/share/fonts/opentype/lobstertwo', '/usr/share/fonts/opentype/cantarell', '/usr/share/fonts/opentype/lobster', '/usr/share/fonts/opentype/roboto', '/usr/share/fonts/opentype/roboto/slab', '/usr/share/fonts/opentype/olga', '/usr/share/fonts/opentype/comic-neue', '/usr/share/fonts/opentype/urw-base35', '/usr/share/fonts/opentype/ebgaramond', '/usr/share/fonts/opentype/neohellenic', '/usr/share/fonts/opentype/cabin', '/usr/local/share/fonts', '/usr/lib/python3/dist-packages/kivy/data/fonts']

# find every font file in in all the font dirs
font_paths = []
for fonts_dir_path in font_dirs:

	for root, dirs, files in os.walk(fonts_dir_path):
		for file in files:
			if file.lower().endswith(".ttf"):
				font_paths.append(str(os.path.join(root, file)))

print( len(font_dirs) )
print( len(font_paths) )
user@buskill:~/tmp/fonts$ 

user@buskill:~/tmp/fonts$ ./font_test.py 
77
6182
user@buskill:~/tmp/fonts$ 

I eliminated symlinks, but I still found the same number of fonts. Also, running this is actually very fast. The issue with kivy crashing the computer is just a kivy GUI issue with so many widgets being rendered on a screen. Perhaps it could be solved by a RecycleView. Or by going back to the normal non-complex OptionItem (if I can figure out how to populate it at runtime).

user@buskill:~/tmp/fonts$ tail -n 13 font_test.py 
# find every font file in in all the font dirs
font_paths = []
for fonts_dir_path in font_dirs:

	for root, dirs, files in os.walk(fonts_dir_path):
		for file in files:
			if file.lower().endswith(".ttf"):
				file_path = os.path.join(root,file)
				if not os.path.islink(file_path):
					font_paths.append(str(file_path))

print( len(font_dirs) )
print( len(font_paths) )
user@buskill:~/tmp/fonts$ 
user@buskill:~/tmp/fonts$ time ./font_test.py
77
6182

real	0m0.043s
user	0m0.024s
sys	0m0.015s
user@buskill:~/tmp/fonts$ 

@maltfield
Copy link
Member

I opened an upstream feature request with the Kivy team to create a standard "FontPicker" SettingItem

This is already standard in other GUI frameworks, such as GTK and .NET

@maltfield
Copy link
Member

Last night I spent several hours trying to adapt our custom ComplexOptions class OptionsItem to be compatible with RecycleView (the positional arguments are an issue; I tried switching to kwargs, but it didn't work)

I also just now tried to load the 6,182 fonts into a normal option widget The normal built-in "option" widget handled the >6,000 entries better than my custom ComplexOption widget, but it still doesn't work:

1. It takes a few seconds to load
2. It doesn't scroll, so a user can only see the first ~10 fonts

TODO: try file picker (defaulting inside the font dir with the most number of TTF files found)

@fmarier
Copy link
Contributor Author

fmarier commented Mar 17, 2024

As for fallbacks to system fonts, I don't think that's possible in python/kivy.

Is there a way to simply not set the font (in case of an exception while looking for it)?

What I had in mind when initially filing this issue was that buskill should load even its font is missing. Yes, ideally, it should load whatever is defined as the default font for serif or sans serif on the system, but if that's too hard, then it should just pick an arbitrary font (ideally not picking anything and just letting the OS free to use whatever it wants). That would be better than not loading at all.

But maybe Kivy has a requirement to specify a font and there's no default or fallback?

@maltfield
Copy link
Member

maltfield commented Mar 17, 2024

If I don't set a font, then it will default to Roboto, which ships with kivy. If Roboto cannot be found, then it will throw an error.

(ideally not picking anything and just letting the OS free to use whatever it wants). That would be better than not loading at all.

A far as I can tell, there is no way to ask the OS to pick the font for us (at least not cross-platform let alone cross-DE). We could pick one arbitrarily, but then we risk picking a font that doesn't have glyphs for the characters we want.

@fmarier
Copy link
Contributor Author

fmarier commented Mar 17, 2024

If I don't set a font, then it will default to Roboto, which ships with kivy. If Roboto cannot be found, then it will throw an error.

That sounds like a reasonable solution then. If Roboto cannot be found in the buskill directory, then try again without setting the font. In that case, it will use Kivy's default (i.e. Roboto in the Kivy directory). If that fails, then that's not buskill's problem and it's instead a bug in the Kivy installation.

@fmarier
Copy link
Contributor Author

fmarier commented Mar 17, 2024

It sounds like the fact that there's no way to honor the OS defaults for fonts is a problem (or maybe feature request) with Kivy. If that ever gets fixed, then buskill will benefit from it automatically.

@maltfield
Copy link
Member

Yes. And I'd say the problem goes higher than kivy. It seems that python in-general should have some cross-platform way to fetch the OS default font.

maltfield added a commit that referenced this issue Mar 18, 2024
This is not working. The biggest issue that I'm having with getting RecycleView working is that I used positional arguments for the creation of BusKillOptionItem()

My understanding is that RecycleView maintains a list of some type of widget. In my case, I want RecycleView to be a set of my custom widget = BusKillOptionItem()s.

But the way that RecycleView creates & recycles these objects is by a dictionary, which gets passed into the objects when they're created as kwargs.

So in this commit I tried changing from positional args to kwargs, but it doesn't appear to be working yet. For more info, see:

 * #55 (comment)
 * https://groups.google.com/g/kivy-users/c/TLZoBXX5zZs
 * https://kivy.org/doc/stable/api-kivy.uix.recycleview.html
@maltfield
Copy link
Member

maltfield commented Mar 18, 2024

I didn't have very good luck with the FilePicker. Namely the path type in the Kivy Settings didn't allow much customization of the FilePicker widget -- such as being able to specify a filter to only show *.ttf files.

I could have made a custom widget for this, but it also looked really bad and I figured it was likely just as much work to keep trying the RecycleView.

Indeed, I had much better luck today getting the fonts to appear in a recycle view. My latest commit displays >500 fonts (not sure why it's less than the 6,000 items before), and (with RecycleView) it loads near-instantaneous and has no lag when clicking around the screen.

This is just a proof of concept. I got the list of fonts to display, but I horribly broke the actual ability to click them and change the settings in the process. The biggest issue now is that my BusKillOptionItem() class used to take positional arguments, but the way that RecycleView is able to spawn custom widget is by passing it an array of dict()s with the values that will be passed into the widget as Kivy Properties.

(this was all very difficult to wrap my head around, and this example was instrumental in helping me get this far https://groups.google.com/g/kivy-users/c/4_xaX7xtL_s)

While I've managed to get ^ that working for the basic arguments (like icon, option value, description, etc), I'm not able to access them from within the object's functions itself -- namely __init__(). This means at least two things are broken:

  1. I can't reach-back to the Config object to get() or set() settings because the object property is None within the object's __init()__ function.
  2. The OptionItem doesn't know what is the currently-set value for the setting, so I can't make the radio button appear "checked" or "unchecked" as-needed

To address this, I've made a simplified example of this issue and posted it on SE:

@maltfield
Copy link
Member

maltfield commented Mar 18, 2024

I was successfully able to get the BusKillOptionItems() created and clickable and saving to the Config object last night.

I had to switch from using __init__() to the kivy on_<property_name>() function. In the case = on_manager(). For more info, see my answer to the SE question linked-above

def on_manager(self, instance, value):
self.manager = value
# the "main" screen
self.main_screen = self.manager.get_screen('main')
# we steal (reuse) the instance field referencing the "modal dialog" from
# the "main" screen
self.dialog = self.main_screen.dialog

Unfortunately, I found some strange bug where if a user scrolls "up" at the top or "left" or "right" then it registers it like a click, so a user just scrolling through the list of fonts will change to the font under their cursor when they scroll at the "end". I've been fighting with this all day with no solution so-far. I created another SE question about this here:

@maltfield
Copy link
Member

maltfield commented Mar 18, 2024

I built the app with Debian 12, and the issue still occurs.

Unfortunately I just discovered that this issue occurs even on the dev branch, so the RecycleView change was a red herring.

Yeah, I just downloaded v0.7.0 and confirmed that the scroll-becomes-a-click is also a bug on the Trigger setting.

maltfield added a commit that referenced this issue Mar 19, 2024
For some reason when someone was simply scrolling through all the options in a ComplexSetting screen and they reached the end, kivy would pass that scroll event as a click event to the OptionItem -- making us change the Config as if they had clicked on it.

I don't know exactly why kivy does this, but it probably has to do with muti-touch gestures. Kivy thinks its better to bind and call all visible widgets (not just the ones touched) because gestures can sometimes end not where they started. This is different, but maybe related:

 * https://kivy.org/doc/stable/guide/inputs.html#touch-event-basics

> By default, touch events are dispatched to all currently displayed widgets. This means widgets receive the touch event whether it occurs within their physical area or not.
>
> This can be counter intuitive if you have experience with other GUI toolkits. These typically divide the screen into geometric areas and only dispatch touch or mouse events to the widget if the coordinate lies within the widgets area.
>
> This requirement becomes very restrictive when working with touch input. Swipes, pinches and long presses may well originate from outside of the widget that wants to know about them and react to them.

The solution to this was to check the touch.button instance field, which was either "left" or one of "scrollup", "scrolldown", "scrollright", and "scrollleft"

 * https://kivy.org/doc/stable/api-kivy.uix.behaviors.compoundselection.html#kivy.uix.behaviors.compoundselection.CompoundSelectionBehavior.goto_node

For more info, see:

 * https://stackoverflow.com/questions/78183125/scrolling-causes-click-on-touch-up-event-on-widgets-in-kivy-recycleview/78183647#78183647
 * #55 (comment)
@maltfield
Copy link
Member

I fixed the scroll-click bug by checking the value of the touch.button inside the on_touch_up(self, touch) function. More info:

@maltfield
Copy link
Member

maltfield commented Mar 24, 2024

In my latest commit, I have eliminated all references in the markup to Roboto fonts. Now fonts will just be rendered using the default_font Setting that's originally setup by Kivy (which defaults to Roboto).

The user can now select a font in the GUI Settings menu, which updates default_font directly, if set.

There's two exceptions to this where I specify a custom font:

  1. bkmono
  2. mdicons

I reference a custom font named "bkmono" in the text markup, which allows me to choose which mono font to use on-launch. By default, it will use RobotoMono.

The startup is also much more robust. If for some reason there's an issue finding the Roboto default fonts, BusKill will search all the Kivy-given font dirs recursively for the Roboto fonts and use whatever it finds as the default.

Of course, the app will still crash if it can't find any Roboto or Material Design Icons font on the system still, but what we have now is far more robust. And we have never had a report from a user indicating an issue with fonts.

Currently it requires 2 restarts of the app to actually apply the changes of the user's font to the app actually using it. I opened this SE question to address how to change all the widget's label's font face during runtime:

@maltfield
Copy link
Member

I've successfully gotten the app to update the font face of all widgets when the user exits the Settings menu, but now I'm struggling with the "reset" button again now that I've switched from using the buskill section's gui_font_face setting, and now I'm using the built-in kivy section's default_font setting.

Now that I'm deleting the kivy section's options when the reset button is pressed, I get a NoOptionError when refresh_values() tries to figure out the default_fonts.

This could be easily fixed by updating my bulid_config()'s function with another Config.setdefaults('kivy', {...}) call, but then I'd have to hardcode the default_font. And since Kivy has changed this value over-time, I think it's better to reference the actual kivy code to set this value for us.

The code for that is here:

https://github.com/kivy/kivy/blob/c492a33f8cf79e89ea7240690feb5f6d25b08389/kivy/config.py#L902-L908

...but I don't understand how to reference it because it's at the root of the file and not in any publicly-exposed method.

I asked this question on SE about this:

maltfield added a commit that referenced this issue Mar 25, 2024
This commit hardcodes the default_font to Roboto.

Actually all I'm doing here is copying these lines from kivy/config.py into our BusKillApp.build_config() function, but I don't like it because if Kivy changes from Roboto to something else in the future, this will break.

 * https://github.com/kivy/kivy/blob/c492a33f8cf79e89ea7240690feb5f6d25b08389/kivy/config.py#L902-L908

The best thing to do would be to somehow *call* the code in the above kivy/config.py script, but I'm not sure how to do it because its not in any method; it's just at the root of the script. I created this question on SE about it here:

 * https://stackoverflow.com/questions/78216476/how-to-get-kivy-to-set-its-defaults-in-the-config-at-runtime

See also:

 * #55 (comment)
@maltfield
Copy link
Member

The latest commit to the font_setting branch now includes otf files (in addition to ttf files) for fonts, so users can now select a custom font in the GUI, including the Open Dyslexic fonts as mentioned in the OP

See the following screenshot of the BusKill App using the Open Dyslexic font

buskill-opendyslexic 20240325

@maltfield maltfield self-assigned this Mar 28, 2024
@maltfield
Copy link
Member

This has been merged into the dev branch

@maltfield
Copy link
Member

maltfield commented Mar 28, 2024

TODO: test on macOS & Windows (blocked by #78)

@maltfield
Copy link
Member

I finally was able to hack together a MacOS build, and I found a bug. The app crashes immediately with failure to find mdicons.ttf

02:30:54,319 buskill_gui DEBUG DEBUG: Found 207 font files.
02:30:54,320 buskill_gui DEBUG DEBUG: Default font = ['Roboto', 'data/fonts/Roboto-Regular.ttf', 'data/fonts/Roboto-Italic.ttf', 'data/fonts/Roboto-Bold.ttf', 'data/fonts/Roboto-BoldItalic.ttf']
02:30:54,320 buskill_gui INFO INFO: Failed to load fonts (File fonts/RobotoMono-Regular.ttf not found)
02:30:54,321 buskill_gui DEBUG DEBUG: Found Roboto Mono ['/Volumes/buskill-1711842980/buskill-1711842980.app/Contents/Frameworks/kivy_install/data/fonts/RobotoMono-Regular.ttf']
02:30:54,321 buskill_gui DEBUG DEBUG: Found Material Icons []
02:30:54,322 buskill_gui WARNING WARNING: Failed to find fonts (list index out of range)
...
02:30:54,453 kivy INFO Base: Start application main loop
02:30:54,467 kivy INFO Base: Leaving application in progress...
02:30:54,470 kivy WARNING stderr: Traceback (most recent call last):
02:30:54,470 kivy WARNING stderr:   File "main.py", line 150, in <module>
02:30:54,471 kivy WARNING stderr:     BusKillApp( bk ).run()
02:30:54,471 kivy WARNING stderr:   File "kivy/app.py", line 956, in run
02:30:54,472 kivy WARNING stderr:   File "kivy/base.py", line 574, in runTouchApp
02:30:54,472 kivy WARNING stderr:   File "kivy/base.py", line 339, in mainloop
02:30:54,472 kivy WARNING stderr:   File "kivy/base.py", line 379, in idle
02:30:54,473 kivy WARNING stderr:   File "kivy/clock.py", line 733, in tick
02:30:54,473 kivy WARNING stderr:   File "kivy/clock.py", line 776, in post_idle
02:30:54,474 kivy WARNING stderr:   File "kivy/_clock.pyx", line 620, in kivy._clock.CyClockBase._process_events
02:30:54,474 kivy WARNING stderr:   File "kivy/_clock.pyx", line 653, in kivy._clock.CyClockBase._process_events
02:30:54,474 kivy WARNING stderr:   File "kivy/_clock.pyx", line 649, in kivy._clock.CyClockBase._process_events
02:30:54,475 kivy WARNING stderr:   File "kivy/_clock.pyx", line 218, in kivy._clock.ClockEvent.tick
02:30:54,475 kivy WARNING stderr:   File "kivy/uix/label.py", line 425, in texture_update
02:30:54,475 kivy WARNING stderr:   File "kivy/core/text/__init__.py", line 836, in refresh
02:30:54,476 kivy WARNING stderr:   File "kivy/core/text/markup.py", line 141, in render
02:30:54,476 kivy WARNING stderr:   File "kivy/core/text/markup.py", line 228, in _pre_render
02:30:54,476 kivy WARNING stderr:   File "kivy/core/text/__init__.py", line 419, in resolve_font_name
02:30:54,477 kivy WARNING stderr: OSError: Label: File 'mdicons.ttf' not found
02:30:54,477 kivy WARNING stderr: [INFO/MainProcess] process shutting down
02:30:54,478 kivy WARNING stderr: [DEBUG/MainProcess] running all "atexit" finalizers with priority >= 0
02:30:54,478 kivy WARNING stderr: [DEBUG/MainProcess] running the remaining "atexit" finalizers

@maltfield
Copy link
Member

maltfield commented Mar 31, 2024

Somehow it looks like the Material Design font is no longer being included in our MacOS builds

From the logs we can see that our app's font dir is part of the list of font dirs returned by Kivy

19:45:11,487 buskill_gui DEBUG DEBUG: system_fonts_dirs:|['/Library/Fonts', '/System/Library/Fonts', '/System/Library/Fonts/Supplemental', '/Users/maltfield/Library/Fonts', '/Volumes/buskill-1711910000/buskill-1711910000.app/Contents/Frameworks/kivy_install/data/fonts'] font files.

But the dir doesn't have the MD font

maltfield@host ~ % ls /Volumes/buskill-1711910000/buskill-1711910000.app/Contents/Frameworks/kivy_install/data/fonts
DejaVuSans.ttf          Roboto-BoldItalic.ttf   Roboto-Regular.ttf
Roboto-Bold.ttf         Roboto-Italic.ttf       RobotoMono-Regular.ttf
maltfield@host ~ % 

It looks like we already have the fonts here

/Volumes/buskill-1711910000/buskill-1711910000.app/Contents/Resources/fonts/MaterialIcons-Regular.ttf

It looks like the stable version of buskill_gui.py had a commented-out line that prepended bk.EXE_DIR.

buskill-app/src/buskill_gui.py

Lines 1118 to 1122 in a585f03

LabelBase.register(
"mdicons",
#os.path.join( bk.EXE_DIR, 'fonts', 'MaterialIcons-Regular.ttf' ),
os.path.join( 'fonts', 'MaterialIcons-Regular.ttf' ),
)

maltfield added a commit that referenced this issue Mar 31, 2024
@maltfield
Copy link
Member

After adding the bk.APP_DIR to the font_dirs list, I was finally able to open the app on MacOS.

Unfortunately, the hamburger menu icon is missing. And, f I slide the navbar from the left with the mouse gesture and click Settings, then the app crashes claiming it can't find our buskill_settings.json file.

20:49:01,612 kivy WARNING stderr:   File "/Volumes/buskill-1711913377/buskill-1711
913377.app/Contents/Frameworks/buskill.kv", line 109, in <module>
20:49:01,613 kivy WARNING stderr:     root.manager.current = 'settings'
20:49:01,613 kivy WARNING stderr: ^^^^^^^^^^^^^^^^^
20:49:01,613 kivy WARNING stderr:   File "kivy/properties.pyx", line 520, in kivy.properties.Property.__set__
20:49:01,613 kivy WARNING stderr:   File "kivy/properties.pyx", line 567, in kivy.properties.Property.set
20:49:01,614 kivy WARNING stderr:   File "kivy/properties.pyx", line 606, in kivy.properties.Property._dispatch
20:49:01,614 kivy WARNING stderr:   File "kivy/_event.pyx", line 1307, in kivy._event.EventObservers.dispatch
20:49:01,614 kivy WARNING stderr:   File "kivy/_event.pyx", line 1213, in kivy._event.EventObservers._dispatch
20:49:01,615 kivy WARNING stderr:   File "kivy/uix/screenmanager.py", line 1063, in on_current
20:49:01,615 kivy WARNING stderr:   File "kivy/uix/screenmanager.py", line 377, in start
20:49:01,615 kivy WARNING stderr:   File "kivy/_event.pyx", line 731, in kivy._event.EventDispatcher.dispatch
20:49:01,615 kivy WARNING stderr:   File "buskill_gui.py", line 966, in on_pre_enter
20:49:01,616 kivy WARNING stderr:     s.add_json_panel( 'buskill', Config, os.path.join(self.bk.SRC_DIR, 'packages', 'buskill', 'settings_buskill.json') )
20:49:01,616 kivy WARNING stderr:   File "kivy/uix/settings.py", line 1044, in add_json_panel
20:49:01,617 kivy WARNING stderr:   File "kivy/uix/settings.py", line 1059, in create_json_panel
20:49:01,617 kivy WARNING stderr: FileNotFoundError: [Errno 2] No such file or directory: '/Volumes/buskill-1711913377/buskill-1711913377.app/Contents/MacOS/packages/buskill/settings_buskill.json'

The files are, in fact, present. And they're the same both for new builds and the last stable release (v0.7.0).

maltfield@5129 ~ % find /Volumes/buskill-* -name settings_buskill.json            /Volumes/buskill-1711913377/buskill-1711913377.app/Contents/Resources/packages/buskill/settings_buskill.json
/Volumes/buskill-v0.7/buskill-v0.7.0.app/Contents/Resources/packages/buskill/settings_buskill.json
maltfield@5129 ~ % 

It appears that this change somehow broke paths

@maltfield
Copy link
Member

ok, so the issue is that my old MacOS release had a bunch of symlinks from Contents/MacOS/* -> Contents/Resources/* but the new one does not

maltfield@host ~ % ls -lah /Volumes/buskill-1711913377/buskill-1711913377.app/Contents/MacOS
total 20440
drwxr-xr-x  4 maltfield  staff   128B Mar 31 20:35 .
drwxr-xr-x  7 maltfield  staff   224B Mar 31 20:35 ..
-rwxr-xr-x  1 maltfield  staff   8.6M Mar 31 20:35 buskill
-r-x------  1 maltfield  staff   1.3M Mar 31 20:35 root_child_mac
maltfield@host ~ %

maltfield@host ~ % ls -lah /Volumes/buskill-v0.7/buskill-v0.7.0.app/Contents/MacOS 
total 29512
drwxr-xr-x  46 maltfield  staff   1.4K Jun 16  2023 .
drwxr-xr-x   7 maltfield  staff   224B Jun 16  2023 ..
-rwxr-xr-x   1 maltfield  staff   271K Jun 16  2023 FLAC
-rwxr-xr-x   1 maltfield  staff   727K Jun 16  2023 FreeType
lrwxr-xr-x   1 maltfield  staff    17B Jun 16  2023 KEYS -> ../Resources/KEYS
-rwxr-xr-x   1 maltfield  staff    47K Jun 16  2023 Ogg
-rwxr-xr-x   1 maltfield  staff   2.3M Jun 16  2023 Python
-rwxr-xr-x   1 maltfield  staff   1.3M Jun 16  2023 SDL2
-rwxr-xr-x   1 maltfield  staff   136K Jun 16  2023 SDL2_image
-rwxr-xr-x   1 maltfield  staff   133K Jun 16  2023 SDL2_mixer
-rwxr-xr-x   1 maltfield  staff    48K Jun 16  2023 SDL2_ttf
-rwxr-xr-x   1 maltfield  staff   931K Jun 16  2023 Vorbis
lrwxr-xr-x   1 maltfield  staff    24B Jun 16  2023 __pycache__ -> ../Resources/__pycache__
lrwxr-xr-x   1 maltfield  staff    29B Jun 16  2023 base_library.zip -> ../Resources/base_library.zip
-rwxr-xr-x   1 maltfield  staff   4.2M Jun 16  2023 buskill
lrwxr-xr-x   1 maltfield  staff    33B Jun 16  2023 buskill-icon-150.png -> ../Resources/buskill-icon-150.png
lrwxr-xr-x   1 maltfield  staff    30B Jun 16  2023 buskill-icon.icns -> ../Resources/buskill-icon.icns
lrwxr-xr-x   1 maltfield  staff    23B Jun 16  2023 buskill.kv -> ../Resources/buskill.kv
lrwxr-xr-x   1 maltfield  staff    27B Jun 16  2023 buskill_cli.py -> ../Resources/buskill_cli.py
lrwxr-xr-x   1 maltfield  staff    27B Jun 16  2023 buskill_gui.py -> ../Resources/buskill_gui.py
lrwxr-xr-x   1 maltfield  staff    31B Jun 16  2023 buskill_version.py -> ../Resources/buskill_version.py
lrwxr-xr-x   1 maltfield  staff    20B Jun 16  2023 certifi -> ../Resources/certifi
lrwxr-xr-x   1 maltfield  staff    21B Jun 16  2023 docutils -> ../Resources/docutils
lrwxr-xr-x   1 maltfield  staff    18B Jun 16  2023 fonts -> ../Resources/fonts
lrwxr-xr-x   1 maltfield  staff    16B Jun 16  2023 gpg -> ../Resources/gpg
lrwxr-xr-x   1 maltfield  staff    19B Jun 16  2023 images -> ../Resources/images
drwxr-xr-x   9 maltfield  staff   288B Jun 16  2023 kivy
lrwxr-xr-x   1 maltfield  staff    25B Jun 16  2023 kivy_install -> ../Resources/kivy_install
drwxr-xr-x  51 maltfield  staff   1.6K Jun 16  2023 lib-dynload
lrwxr-xr-x   1 maltfield  staff    30B Jun 16  2023 libassuan.0.dylib -> ../Resources/libassuan.0.dylib
-rwxr-xr-x   1 maltfield  staff   2.3M Jun 16  2023 libcrypto.1.1.dylib
lrwxr-xr-x   1 maltfield  staff    31B Jun 16  2023 libgcrypt.20.dylib -> ../Resources/libgcrypt.20.dylib
lrwxr-xr-x   1 maltfield  staff    33B Jun 16  2023 libgpg-error.0.dylib -> ../Resources/libgpg-error.0.dylib
lrwxr-xr-x   1 maltfield  staff    28B Jun 16  2023 libintl.8.dylib -> ../Resources/libintl.8.dylib  
lrwxr-xr-x   1 maltfield  staff    22B Jun 16  2023 libintl.a -> ../Resources/libi
lrwxr-xr-x   1 maltfield  staff    26B Jun 16  2023 libintl.dylib -> ../Resources/libintl.dylib
-rwxr-xr-x   1 maltfield  staff   197K Jun 16  2023 liblzma.5.dylib
lrwxr-xr-x   1 maltfield  staff    28B Jun 16  2023 libnpth.0.dylib -> ../Resources/libnpth.0.dylib
-rwxr-xr-x   1 maltfield  staff   288K Jun 16  2023 libreadline.8.dylib
-rwxr-xr-x   1 maltfield  staff   510K Jun 16  2023 libssl.1.1.dylib
lrwxr-xr-x   1 maltfield  staff    29B Jun 16  2023 libusb-1.0.dylib -> ../Resources/libusb-1.0.dylib
lrwxr-xr-x   1 maltfield  staff    20B Jun 16  2023 main.py -> ../Resources/main.py
-rwxr-xr-x   1 maltfield  staff   226K Jun 16  2023 modplug
-rwxr-xr-x   1 maltfield  staff   271K Jun 16  2023 mpg123
lrwxr-xr-x   1 maltfield  staff    21B Jun 16  2023 packages -> ../Resources/packages
-r-x------   1 maltfield  staff   609K Jun 16  2023 root_child_mac
maltfield@host ~ % 

maltfield added a commit that referenced this issue Mar 31, 2024
This commit updates the SRC_DIR from the macos .app/Contents/MacoOS/ dir to .app/Contents/Resources/ dir

Apparently PyInstaller used to put symlinks inside the .app/Contents/MacOS dir for things like 'packages' and 'fonts' to go back to the ../Resources/ dir, but that no longer happens

 * pyinstaller/pyinstaller#7713
 * #55 (comment)

now we'll point at the actual file, not the no-longer-existing symlink
maltfield added a commit that referenced this issue Jul 29, 2024
This commit replaces the Config item name "buskill" with "buskill_trigger"

Apparently I silently renamed this option in the Config file some months ago when I was switching the Settings screen to use RecycleView, for the font accessibility feature

 * #55 (comment)

I don't know exactly why I made the change, but apparently I already updated it in other places, except here. The result: a bug where the first time you "arm" BusKill, it always defaulted to the 'lock-screen' trigger, instead of the setting the user already set it to.

 * #77 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants