I'm writing a program with Kivy.
Under "desktop" OSes it would be normal to use the keyboard for input, and my code does so.
class ProgLayout( FloatLayout ):
def __init__(self, **kwargs):
super( ProgLayout, self ).__init__(**kwargs)
# Keyboard Handling (if any)
self.keyboard = Window.request_keyboard(self.keyboard_closed, self)
self.keyboard.bind(on_key_down=self.on_keyboard_down)
self.keyboard.bind(on_key_up=self.on_keyboard_up)
self.key_states = { 'up':False, 'down':False, 'left':False, 'right':False }
def on_keyboard_down( self, keyboard, keycode, text, modifiers ):
#print("ON_KEYBOARD_DOWN")
#print('The key', keycode, 'have been pressed')
#print(' - text is %r' % text)
#print(' - modifiers are %r' % modifiers)
junk, key = keycode
keymap = { 'up':Directions.NORTH, 'down':Directions.SOUTH, 'left':Directions.WEST, 'right':Directions.EAST }
if ( key in ( 'up', 'down', 'left', 'right' ) ):
self.key_states[ key ] = True
self.handleKeys()
def on_keyboard_up( self, keyboard, keycode ):
#print("ON_KEYBOARD_UP")
junk, key = keycode
if ( key in ( 'up', 'down', 'left', 'right' ) ):
self.key_states[ key ] = False
self.handleKeys()
However, when the app starts under Android (I suspect) opening the keyboard induces the onscreen-keyboard to appear. Obviously the soft-keyboard can be dismissed, but a better solution is to only open the keyboard if a hardware keyboard is present - or just not automatically use it for Android.
Is there a way to determine if a physical keyboard is present on Kivy?
Is there a way to determine if my app is running on Android (ah, I guess platform.system() or sys.platform).
EDIT: buildozer fails to build the .APK if I include sys or platform in the buildozer.spec manifest. Importing sys still seems to work despite this. But on my android tablet, sys.platform reports "linux", so that's not a way forward.
As the commenter #Erik points out, the solution to determining the current platform is kivy.utils.platform.
from kivy.utils import platform
...
if ( kivy.utils.platform != 'android' ):
# open the keyboard, since we're not android
As the documentation states:
A string identifying the current operating system. It is one of: ‘win’, ‘linux’, ‘android’, ‘macosx’, ‘ios’ or ‘unknown’.
For what concerns the platform, it's sufficient to:
import kivy
print(kivy.platform)
For what concerns the virtual keyboard, there is the VKeyboard module in Kivy, but I can't really read any of the attributes this class should have... strange enough.
Related
Simply put, I would like to use a small USB keypad/numpad to type into a text input field on a kivy app running on android via the kivy launcher.
I am using an OTG USB connector to connect a small keypad to my tablet. The connector works in general on the tablet (I can type in numbers in the browser and other apps). In a kivy app however, when I use the touchscreen to select a text input field, it focuses awaiting input, then if I try to type in with the numpad, nothing happens. After doing some testing with a normal sized USB keyboard, it works (but the numpad on it doesn't).
Here is the basic kivy app I used to test this out with:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
KV = """
<MyScreenManager>:
KeyboardInput:
name: 'KeyInput'
<KeyboardInput>:
GridLayout:
rows: 3
TextInput:
id: input1
text:''
multiline: False
Widget:
TextInput:
id: input2
text:''
multiline: False
"""
class KeyboardInput(Screen):
pass
class MyScreenManager(ScreenManager):
def changescreen(self, value):
try:
if value!='Go To...':
self.current = value
except:
print('No screen named ' + value)
#Main application
class packagingDiscardmentApp(App):
def build(self):
Builder.load_string(KV)
self.sm = MyScreenManager()
return self.sm
if __name__ == '__main__':
packagingDiscardmentApp().run()
My guess is that there is some issue with the ascii characters on the numpad and Kivy. Any help would be greatly appreciated.
System: Qt/QML 5.3.1 Android with Windows QtCreator
Device: Samsung Tab 3 8", with Android 4.1.2
EDIT:
My Main QML page contains a TextInput with Keys.onPressed. This TextInput receives only the DEL key and not other keys from standard virtual keyboard.
How to do to receive all keys in the TextInput/Keys.onPressed event handler?
import QtQuick 2.2
import QtQuick.Window 2.1
import QtQuick.Controls 1.2
ApplicationWindow {
visible: true
width: 640
height: 400
toolBar: ToolBar {
Row {
anchors.fill: parent
ToolButton {
text: "Exit"
onClicked: Qt.quit();
}
}
}
TextInput {
width: 200
height: 40
focus: true
Keys.onPressed: {
console.log("Key="+event.key+" "+event.text);
}
Keys.onReleased: {
console.log("Key="+event.key+" "+event.text);
}
}
}
An error is raised by Qt when I'm pressing a key on virtual keyboard, including the DEL key:
W/Qt (26304): kernel\qmetaobject.cpp:1458 (static bool QMetaObject::invokeMethod(QObject*, const char*, Qt::ConnectionType, QGenericReturnArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument, QGenericArgument)): QMetaObject::invokeMethod: No such method QQuickTextInput::inputMethodQuery(Qt::InputMethodQuery,QVariant)
EDIT (27.10.2014):
This problem occurs because it is a missing functionality in Qt/Qml, see the following link for discussion about it http://qt-project.org/forums/viewthread/45072/ and the following link for the report to Qt https://bugreports.qt-project.org/browse/QTBUG-40803
Analysis
As the question author already found, this problem is due to missing functionality in Qt for Android (see QTBUG-40803):
On Samsung devices, like tablets and smartphones, it is impossible to receive keys from the virtual keyboard [from a TextInput component], by using Keys.onPressed or Keys.onRelease, except for DEL and ENTER keys.
The issue is not limited to Samsung devices as told in the bug report; it's also happening on my Asus Nexus 7, for example. The DEL key mentioned in the bug report is the key ⌫ (Delete / Backspace) of the Android keyboard, not Delete (which the Android keyboard does not have). The key events for this key are not emitted every time, but only when pressed in an empty TextField.
This behavior is different from Qt desktop applications, where every keypress / key release event of a physical key is signaled by TextInput.
Solution
You cannot listen to Keys.onPressed / Keys.onReleased, but you can listen to the onTextEdited and onTextChanged signals of TextInput. For simple cases like to enable a button when the field contains text, these signals are enough. For other cases, you could analyze what text was typed and take action based on the key used for that.
This solution is further complicated by two other bugs:
On the Android platform, the textChanged() and textEdited() signals are not emitted by TextInput as long as predictive text input is enabled for the Android keyboard (source). I suppose they are emitted eventually but only after "committing" a word by typing the space character or tapping on a suggestion. That does not help if you want to react to key presses, of course. So you first have to disable predictive text input behavior with TextInput { inputMethodHints: Qt.ImhNoPredictiveText }.
On some devices, TextInput { inputMethodHints: Qt.ImhNoPredictiveText } has no effect (QTBUG-37533). Then, inputMethodHints: Qt.ImhSensitiveData is an alternative. Works on Asus 7. (This does not create a "password entry field" – text will still be shown in the field, but predictive input etc. is switched off.)
Taken together, a working solution (for me on Nexus 7) looks like this:
TextField {
inputMethodHints: Qt.ImhSensitiveData
onTextEdited: {
console.log("textEdited() signal received")
}
in Kivy, when I press the back button on my android device it throws me out of the application. is there a way to return back to the previous screen using the Kivy language and not python? this is what I have written in kivy:
<MyAppClass>:
AnchorLayout:
anchor_x : 'center'
anchor_y : 'top'
ScreenManager:
size_hint : 1, .9
id: _screen_manager
Screen:
name:'screen1'
StackLayout:
# irrelevant code
Screen:
name:'screen2'
StackLayout:
# irrelevant code
I need to manipulate the screen manager and its screens from python... if I can do so I will be ok with python.
Kivy on android binds the back button to the esc button so binding and listening to esc button in your app would help you handle how your app behaves when the back button is pressed.
In other words in your app when testing it on your desktop listen to the escape key from the system keyboard, this will be automatically be translated to being the back button on your android device. Something like::
def on_start():
from kivy.base import EventLoop
EventLoop.window.bind(on_keyboard=self.hook_keyboard)
def hook_keyboard(self, window, key, *largs):
if key == 27:
# do what you want, return True for stopping the propagation
return True
i guess that i have solved it but should thank both #inclement and #qua-non! your answers guys led me to the right way! so in kv i assume that i gave an id to my screen manager (please refer to my question where i have written the kv code) , in python i should do the following:
from kivy.core.window import Window
from kivy.properties import ObjectProperty
class MyAppClass(FloatLayout):#its a FloatLayout in my case
_screen_manager=ObjectProperty(None)
def __init__(self,**kwargs):
super(MyAppClass,self).__init__(**kwargs)
#code goes here and add:
Window.bind(on_keyboard=self.Android_back_click)
def Android_back_click(self,window,key,*largs):
if key == 27:
self._scree_manager.current='screen1'#you can create a method here to cache in a list the number of screens and then pop the last visited screen.
return True
class MyApp(App):
def build(self):
return MyAppClass()
if __name__=='__main__':
MyApp().run()
This is certainly possible. Here's a short example app with the method I use to do this:
from kivy.utils import platform
from kivy.core.window import Window
class ExampleApp(App):
manager = ObjectProperty()
def build(self):
sm = MyScreenManager()
self.manager = sm
self.bind(on_start=self.post_build_init)
return sm
def post_build_init(self, *args):
if platform() == 'android':
import android
android.map_key(android.KEYCODE_BACK, 1001)
win = Window
win.bind(on_keyboard=self.my_key_handler)
def my_key_handler(self, window, keycode1, keycode2, text, modifiers):
if keycode1 in [27, 1001]:
self.manager.go_back()
return True
return False
This should give the right basic idea, but a few notes:
ScreenManager doesn't keep track of the previous screens, it's up to you to implement this how you like. My example assumes you defined a class MyScreenManager with a go_back method.
It might not be necessary to bind to on_start and run post_build_init, this is just how the example I originally used did it (see below). It might be important sometimes though, possibly if the window is not initialised when build() is run, and the original mailing list post suggests the author needed it for some reason.
The example listens for keycodes 27 or 1001. As qua-non said while I was writing this, the former listens for esc, so you can get the same behaviour on desktop.
I didn't try without the android.map_key line, but it seems like it may not be necessary.
You mention you want to use kivy language and not python. You need to do some python to get this result, and I don't see a way around that (it's not really the domain of the kv language). I guess you could shift some stuff to kv by defining a 'go_back' event somewhere and triggering this when the key is pressed, along with binding your screenmanager to watch that event, but it seems like a long way around.
I based my code on the mailing list thread at https://groups.google.com/forum/#!topic/kivy-users/7rOZGMMIFXI . There might be a better way, but this is quite functional.
Now all the way in 2020 I'm using:
Clock.schedule_once(lambda x: Window.bind(on_keyboard=self.hook_keyboard))
in combination with a similar hook_keyboard method to the other answers, to delay the bind in my build method. Works fine, but none of these other ways ways seemed to work for me anymore.
Looks silly, but I can't get meta state out of KeyEvent, accessed from onKeyListener. Tried with all keyboards I have and with emulators.
Whether or not Shift, Ctrl, etc keys are pressed, keyEvent.getMetaState() returns 0. May it works for TextListener, but I don't need it for entering text, I just want to differentiate between Tab and Shift+Tab.
Thanks for anticipated help.
Update. What I just figured out is that meta state is reported for alphabetic keys, but not for other keys.
For example if I press left Shift+T the system generates KeyEvent for KEYCODE_SHIFT_LEFT and KEYCODE_T, and KeyEvent for KEYCODE_T has META_SHIFT_ON set. You can trick the system with Shift+TAB+T, in which case META_SHIFT_ON is set for both KEYCODE_T and KEYCODE_TAB. However with Shift+TAB the KeyEvent for KEYCODE_SHIFT_LEFT is not generated, and the meta state remains unaffected.
Maybe system keyboard configuration files need to be updated to allow combinations like Shift+TAB?
Yes, it's really about android configuration file.
You modify TAB entry in /system/usr/kychars/Generic.kcm (or the file for corresponding vendor) and add a line for shift, as the following:
key TAB {
label: '\t'
base: '\t'
shift: '\t'
ctrl, alt, meta: none
}
Some devices uses qwerty.kcm, which has the shift line alaready present. They should work OK without intrusion. BTW, unicode has provides a special code \u21B9 for Shift+TAB, but it might not be recognised by Android.
I want to create keyboard shortcuts in my Android app such that, say, alt-t runs a certain command. I can't seem to figure out how to detect the presence of the alt modifier in the emulator, though.
I've overridden onKeyDown() in my app to look like the following (Scala):
override def onKeyDown(keycode:Int, event:KeyEvent) = keycode match {
case KeyEvent.KEYCODE_B =>
StatisticsService.map(_.sayBatteryLevel())
true
case KeyEvent.KEYCODE_D =>
StatisticsService.map(_.sayDate())
true
case KeyEvent.KEYCODE_S =>
StatisticsService.map(_.saySignalStrengths())
true
case KeyEvent.KEYCODE_T =>
StatisticsService.map(_.sayTime())
true
case _ => super.onKeyDown(keycode, event)
}
That of course matches the plain keys just fine, but not alt-b, alt-t, etc. How can I change the above to match the given alt-modified bindings?
I've searched Google and have tried using event.isAltPressed, but this doesn't work. I've also logged the results of the keypresses, and have noticed that the alt key isn't at all picked up. That is, simply pressing alt does nothing, and pressing alt-t produces identical logs to just t.
This is being tested in the emulator if that makes a difference.
Edit: Not sure how to respond to comments left on my question, but I'm aware that not all devices have alt keys, but that's not an issue here because I'm coding for a custom device that does. The question isn't "what's the most generic way to do this," but "how do I match alt keybindings when the documented methods don't work and no explanation seems to be given in Google's own docs?"
Thanks.