Python/Kivy: initialize switch after startup (w/o running on_active) - python

I am facing a problem with the Kivy widget Switch and was not able to find a solution. Each topic on the Internet deals with "Working with the active property", which is understandable to me. But I want to set/initializie the start-up active value depending on the current environment within the program.
In my case: I have a Wifi Power-Plug which can be already running. So in this case when the app starts I want the switch with active: True. If the plug is deactivated, the switch shall start with active: False
Normaly you can do this from the main.py with sth. like:
if (getWifiState) == "OFF":
self.ids[widgetName].active = False
else:
self.ids[widgetName].active = True
Generally spoken this works and changes the state.
But here the problem: as soon as you are changing the switch value this way it behaves as if you were clicking on the switch, because the default value = 0 → change to 1 → on_active: function() will be called. But I need a solution which allows me just to change the start value without running the on_active property.
Potential solution:
Probably I have to put logic into my .kv file so that during the switch initialisation the correct start parameter will be set. But why?
Or is there another way to do so?
Appreciate your help
Tried to put logic to my active property in .kv-File, but this did not work.

My solution:
import random
from kivy.app import App
from kivy.lang import Builder
kv = '''
BoxLayout:
Switch:
active: app.get_wifi_state()
on_active: print(self.active)
'''
class Test(App):
# method which return wifi status (replace implementation with your own)
def get_wifi_state(self):
return random.choice((True, False))
def build(self):
return Builder.load_string(kv)
Test().run()

Related

Kivy, Python 3.8 - Unable to get SoundLoader working properly

I'm making an app/game and the user is able to play a sound when pressing the sound button, and the same sound is played after the user finishes a level. The next level contains a new sound.
For some reason, my code doesn't act how I want it and the results aren't consistent: sometimes it works only once, sometimes it works twice or more. Sometimes the next level contains the new sound but most of the time it doesn't play anything.
It probably has something to do with the way I load/try to unload audio files because the code returns 'play' when printing out the audio state.
Here is a simplified version of the code:
import time
import kivy
from kivy.app import App
from kivy.core.audio import SoundLoader
from kivy.lang import Builder
from kivy.uix.widget import Widget
root_widget = Builder.load_file('app.kv')
class ExampleWidget(Widget):
def play_sound(self):
file = 'correct.wav'
print('playing ', file)
click_sound = SoundLoader.load(file)
click_sound.play()
time.sleep(2)
print(click_sound.state)
click_sound.stop()
print(click_sound.state)
#click_sound.unload() # Crashes the app
click_sound = ''
class MyApp(App):
def build(self):
return ExampleWidget()
MyApp().run()
app.kv:
<ExampleWidget>:
id: ew
GridLayout:
col: 2
rows: 3
size: root.size
Button:
text: 'play sound'
size: self.size
on_release: ew.play_sound()
I also tried to empty the click_sound variable by assigning it a None value, using 'del click_sound', tried emptying the variable at the start of the function, end or both and I also tried using the unload() function but that returns the following error:
Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
I'm also getting the following warning. I don't think it's a part of my problem but I'm adding it just in case:
[WARNING] [ffpyplayer ] [wav # 0x7fc738908e00] Discarding ID3 tags because more suitable tags were found.
Hopefully someone can help me out, I thank you in advance.
EDIT:
As ApuCoder said, the code works fine, just not on my macbook. Just tested it on a Windows pc and there were no problems. Does anybody know a way to fix this on Apple computers?

Kivy: set widget disabled property in python code

I have something like this in kivy lang file (pseudo code)
<RootWidget>:
Checkbox:
id: chkbox
TextInput:
id: in_text
text: ""
Button:
id: ok_btn
label: "Okay"
on_press: app.ok_pressed()
disabled: chkbox.active or len(in_text.text) > 8 and ...
The point is, the ok_btn needs to be enabled and disabled dynamically based on state of several other widgets.
This all works as expected, but now I have a problem. For complicated reasons, I need to create the button and insert it into the root widget in python rather than define it in a .kv file or string. I can't figure out what to do with the disabled property. If I set it as a property
btn = Button()
btn.disabled = ...
This only sets the initial state. I thought maybe
btn.bind(on_disabled=some_function)
but this is only to do something when the button is disabled, not to define when it should be disabled. Ditto on_state. I also tried
btn.bind(disabled=some_function)
some_function is never called
Thanks in advance for any pointer
It sounds like you have it backwards: it isn't the button's disabled property that you want to bind things to, but rather you want to bind to other things so that when they change the button's disabled property gets updated.
For instance, from your original example the autogenerated code is along the lines of chkbox.bind(active=lambda self: setattr(ok_btn, "disabled", self.active) (not actually this code, but something equivalent). You need to replicate this manually.
Of course you can abstract this in various ways. For instance, you could bind all the conditions you care about to update a property of your App class (so that it's always present to update, regardless of whether your button exists), then use a kv rule like disabled: app.that_property in your button. This is not the only option though.

kivy executes "on_release"-command multiple times

To trigger the acquisition of an image, I use the on_release-function of a kivy-button.
So, whenever this button is clicked (or pressed -- since using a touchscreen) a camera is triggered using gphoto2.
The issue:
From time to time, the function is executed multiple times (taking multiple images), while it was clearly pressed only a single time.
According to the logs, I'm confident, that it's a kivy-related issue (not related to the camera, etc.): Logging entries within the on_release-function appear multiple times within the logs.
I'm running an app with kivy (version 1.9.0) and python (version 2.7.6) under Ubuntu 14.04 LTS (64bit) using a touchscreen.
Any hint on how to debug or fix the issue is welcome.
I was stuck at the same problem for days!
Do you have this line in your code?
Config.set('input', 'mouse', 'mouse, multitouch_on_demand')
If yes, then remove it.
On touch devices, touch gets triggered multiple times.
Hope it helps someone with the same problem.
You could disable the button when the first release event starts, and in the end of the thread where the picture is taken, enable the button again. Then you will not have multiple events from that button executing, but still the main app thread is allowed to continue.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
import time
import threading
Builder.load_string('''
<MyLayout>:
Button:
text: 'Print to terminal'
on_release:
root.button_released(self)
''')
class MyLayout(BoxLayout):
def button_released(self,button):
button.disabled = True
print("{} pressed!".format(button))
threading.Thread(target=self.take_picture, args=(button,)).start()
def take_picture(self,button):
time.sleep(1) # taking picture or whatever
button.disabled = False
class MyApp(App):
def build(self):
return MyLayout()
MyApp().run()

Python Kivy: Properly start a background process that updates GUI elements

I have a Python script that performs some intensive processing of user's files and can take some time. I've build a user interface to it using Kivy, that allows the user to select the file, processing mode and shows them some messages as the process goes on.
My problem is that when the main Kivy loop passes calls the underlying user interface, the window freezes.
From what I've understood, the proper way of resolving this is to create a separate process to which the script would be off-loaded and from which it would send the updates to the user interface.
However, I was not able to find an example of how to do this or any specification on how to send messages from a separate thread back into application.
Could someone please give an example of how to do this properly or point me to the documentation pertaining to the subject?
Update:
For the sake of keeping the program maintainable I would like to avoid calling the elements of loops of processor from the main thread and instead call one long process that comes back to updated elements of the GUI, such as the progress bar or a text field. It looks like those elements can be modified only from the main kivy thread. How do I gain access to them from the outside?
Use publisher/consumer model as described here. Here's an example from that link modified to use separate threads:
from kivy.app import App
from kivy.clock import Clock, _default_time as time # ok, no better way to use the same clock as kivy, hmm
from kivy.lang import Builder
from kivy.factory import Factory
from kivy.uix.button import Button
from kivy.properties import ListProperty
from threading import Thread
from time import sleep
MAX_TIME = 1/60.
kv = '''
BoxLayout:
ScrollView:
GridLayout:
cols: 1
id: target
size_hint: 1, None
height: self.minimum_height
MyButton:
text: 'run'
<MyLabel#Label>:
size_hint_y: None
height: self.texture_size[1]
'''
class MyButton(Button):
def on_press(self, *args):
Thread(target=self.worker).start()
def worker(self):
sleep(5) # blocking operation
App.get_running_app().consommables.append("done")
class PubConApp(App):
consommables = ListProperty([])
def build(self):
Clock.schedule_interval(self.consume, 0)
return Builder.load_string(kv)
def consume(self, *args):
while self.consommables and time() < (Clock.get_time() + MAX_TIME):
item = self.consommables.pop(0) # i want the first one
label = Factory.MyLabel(text=item)
self.root.ids.target.add_widget(label)
if __name__ == '__main__':
PubConApp().run()
I think it's worth providing a 2022 update. Kivy apps can now be run via Python's builtin asyncio library and utilities. Previously, the problem was there was no way to return control to the main Kivy event loop when an async function call finished, hence you could not update the GUI. Now, Kivy runs in the same event loop as any other asyncio awaitables (relevant docs).
To run the app asynchronously, replace the YourAppClass().run() at the bottom of your main.py with this:
import asyncio
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(
YourAppClass().async_run()
)
loop.close()
And that's about it. With regards to the docs:
It is fully safe to interact with any kivy object from other
coroutines running within the same async event loop. This is because
they are all running from the same thread and the other coroutines are
only executed when Kivy is idling.
Similarly, the kivy callbacks may safely interact with objects from
other coroutines running in the same event loop. Normal single
threaded rules apply to both case.
If explicitly need to create a new thread, #Nykakin 's approach is what you want. I'd recommend using Queues to pass data between threads, instead, because they're simpler to implement and more robust, being specifically designed for this purpose. If you just want asynchronicity, async_run() is your best friend.
BE WARNED: While modifying a kivy property from another thread nominally works, there is every indication that this is not a thread safe operation. (Use a debugger and step through the append function in the background thread.) Altering a kivy property from another thread states that you should not modify a property in this way.

Pause a python script until an event occurs without hanging/blocking the GUI

Trying to migrate from PyQt with Kivy and I cant even imagine a solution for this.
I have thousands of lines of code that use Qt's dialogues for text input. That is, when their line of code is reached, they 'stop' the script until the "ok" button is pressed, so they can return the text input.
Kivy doesnt have that functionality, so ideally, when the program needs user input, the "ok" button would call for the next function to run.
Therefore I must replace all the current calls to a PyQt function with a function that stops the running script, launches a working responsive dialogue, then resumes the original when it has the text input it requested. So the question is:
Is there a way to stop a running script until a function finishes, without hanging the GUI?
I have already tried:
Threading:
Even if I start the text input in a new thread:
t = threading.Thread(target=TextInput.waiter)
the function that calls such thread will return just after calling the text input.
If I use this code:
t.start()
t.join()
The main script will stop, but also hangs the text input GUI.
While/Sleep: Waiting for the text input variable to contain a valid result. But this blocks the ongoing textinput GUI in Kivy
Hacking raw_input:
Currently thinking into try some hack with that, that would allow me to stop the script, then feed back the input found by the kivy text input popup.
Any pointers would be really welcome, thanks for reading.
You can't just pause the running script. Instead, you'll need to refactor your program to be event-driven (as Kivy is an event-driven GUI).
Here's a simple example function:
def myfunc():
# do some stuff here
# now we need some input...
val = qt_input_dialogue()
# do some more stuff here
Refactored:
class MyPopup(Popup):
value = StringProperty() # bind this to a TextInput or something
def myfunc1():
# do some stuff here
p = MyPopupClass()
p.bind(on_dismiss=lambda *_: myfunc2(p.value))
p.open()
def myfunc2(val):
# do some more stuff here
If you're willing to use Twisted, you can make this even easier using Deferreds and inlineCallbacks.
from kivy.support import install_twisted_reactor
install_twisted_reactor()
from twisted.internet import defer
Builder.load_string('''
<MyPopup>:
BoxLayout:
orientation: 'vertical'
TextInput:
id: text_input
BoxLayout:
orientation: 'horizontal'
Button:
text: 'OK'
on_press: root.okfn(text_input.text)
''')
class MyPopup(Popup):
def show(self, *args):
d = defer.Deferred()
self.okfn = d.callback
self.open(*args)
return d
#defer.inlineCallbacks
def myfunc():
# do some stuff here
val = yield MyPopup().show()
# do some more stuff here
This way, you can just replace the calls to QT input dialogues with yield MyPopup().show().

Categories