mirror of
https://github.com/keymanapp/keyman.git
synced 2026-10-13 17:37:31 +00:00
Merge pull request #15020 from keymanapp/chore/A19S14-merge-master-into-win-arm
auto: A19S14 merge master into win-arm 🦾
This commit is contained in:
commit
f0ec10f390
56 changed files with 2127 additions and 325 deletions
52
HISTORY.md
52
HISTORY.md
|
|
@ -1,5 +1,48 @@
|
|||
# Keyman Version History
|
||||
|
||||
## 19.0.144 alpha 2025-10-23
|
||||
|
||||
* chore(deps-dev): bump playwright from 1.46.1 to 1.56.1 (#14984)
|
||||
* chore(web): revert "chore(deps-dev): bump playwright from 1.46.1 to 1.56.1" (#15000)
|
||||
|
||||
## 19.0.143 alpha 2025-10-20
|
||||
|
||||
* fix(android): Set the main app background white (#14935)
|
||||
|
||||
## 19.0.142 alpha 2025-10-17
|
||||
|
||||
* chore(common): add .model_info schema (#14967)
|
||||
* chore(web): update helpURL documentation and float ui link (#14960)
|
||||
* fix(linux): use `ibus` command to restart ibus (#14971)
|
||||
* chore(linux): replace deprecated `parse_version` (#14973)
|
||||
|
||||
## 19.0.141 alpha 2025-10-16
|
||||
|
||||
* refactor(linux): extract functions (#14937)
|
||||
* refactor(linux): remove no-longer-needed Python 3.7 code (#14956)
|
||||
* chore(linux): don't report errors to Sentry when running autopkgtests (#14938)
|
||||
* fix(linux): fix installing keyboard in shared area (#14953)
|
||||
* fix(linux): install schema during `keyman-config/build.sh install` (#14962)
|
||||
* test(linux): add regression tests for cli tools to autopkgtests (#14950)
|
||||
|
||||
## 19.0.140 alpha 2025-10-15
|
||||
|
||||
* fix(mac): add Microsoft Word to non-compliant app list (#14940)
|
||||
|
||||
## 19.0.139 alpha 2025-10-13
|
||||
|
||||
* fix(windows): osk: handle simulated left Control event when AltGr pressed (#14909)
|
||||
|
||||
## 19.0.138 alpha 2025-10-11
|
||||
|
||||
* fix(android): Update Sample1 text field to resize (#14922)
|
||||
|
||||
## 19.0.137 alpha 2025-10-10
|
||||
|
||||
* chore(common): Update crowdin strings for Vietnamese (#14915)
|
||||
* maint(resources): fix watch bug for common files, and add more logging for triggering builds (#14893)
|
||||
* docs(developer): Update keyboard-editor.md to clarify when entire row will be deleted (#14919)
|
||||
|
||||
## 19.0.136 alpha 2025-10-09
|
||||
|
||||
* fix(android): Refactor bottom inset calculations for in-app and system keyboard (#14873)
|
||||
|
|
@ -846,6 +889,15 @@
|
|||
* refactor(windows): rename `TKeymanMutex.MutexOwned` to `TakeOwnership` and add `ReleaseOwnership` (#13168)
|
||||
* chore: increment to alpha 19.0 (#13187)
|
||||
|
||||
## 18.0.243 stable 2025-10-15
|
||||
|
||||
* chore(linux): Update debian changelog (#14855)
|
||||
* fix(android): Convert longpress delay menu to ConstraintLayout (#14906)
|
||||
* fix(linux): replace `dbus-x11` dependency (#14898)
|
||||
* chore(ios): renew certificate (#14917)
|
||||
* fix(android): Refactor bottom inset calculations for inapp and system keyboard (#14913)
|
||||
* fix(mac): add Microsoft Word to non-compliant app list (#14951)
|
||||
|
||||
## 18.0.242 stable 2025-09-29
|
||||
|
||||
* maint(linux): remove Oracular, add Questing (#14781)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
19.0.137
|
||||
19.0.145
|
||||
|
|
@ -619,11 +619,14 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
getTheme().resolveAttribute(android.R.attr.actionBarSize, outValue, true);
|
||||
int actionBarHeight = getResources().getDimensionPixelSize(outValue.resourceId);
|
||||
|
||||
// *** TO DO: Try to check if status bar is visible, set statusBarHeight to 0 if it is not visible ***
|
||||
// Status bar height only used for portrait orientation
|
||||
int statusBarHeight = 0;
|
||||
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
|
||||
if (resourceId > 0) {
|
||||
statusBarHeight = getResources().getDimensionPixelSize(resourceId);
|
||||
if (lastOrientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
// *** TO DO: Try to check if status bar is visible, set statusBarHeight to 0 if it is not visible ***
|
||||
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
|
||||
if (resourceId > 0) {
|
||||
statusBarHeight = getResources().getDimensionPixelSize(resourceId);
|
||||
}
|
||||
}
|
||||
int navigationBarHeight = KMManager.getNavigationBarHeight(context, KeyboardType.KEYBOARD_TYPE_INAPP);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
android:fitsSystemWindows="true"
|
||||
android:layout_above="@+id/KMKeyboard"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/yellow_background"
|
||||
android:background="@android:color/white"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<include layout="@layout/titlebar"
|
||||
|
|
|
|||
|
|
@ -174,15 +174,10 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
// *** TO DO: Try to check if status bar is visible, set statusBarHeight to 0 if it is not visible ***
|
||||
int statusBarHeight = 0;
|
||||
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
|
||||
if (resourceId > 0)
|
||||
statusBarHeight = getResources().getDimensionPixelSize(resourceId);
|
||||
|
||||
// Navigation bar height
|
||||
int navigationBarHeight = 0;
|
||||
resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android");
|
||||
if (resourceId > 0) {
|
||||
navigationBarHeight = getResources().getDimensionPixelSize(resourceId);
|
||||
statusBarHeight = getResources().getDimensionPixelSize(resourceId);
|
||||
}
|
||||
int navigationBarHeight = KMManager.getNavigationBarHeight(context, KeyboardType.KEYBOARD_TYPE_INAPP);
|
||||
|
||||
Point size = KMManager.getWindowSize(context);
|
||||
int screenHeight = size.y;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
|
|
@ -5,16 +6,16 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
android:layout_above="@+id/KMKeyboard"
|
||||
android:orientation="vertical"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<com.keyman.engine.KMTextView
|
||||
android:id="@+id/kmTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
android:inputType="textMultiLine"
|
||||
android:inputType="textMultiLine|textNoSuggestions"
|
||||
android:gravity="top"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
|
|
|||
19
common/schemas/model_info/README.md
Normal file
19
common/schemas/model_info/README.md
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# model_info
|
||||
|
||||
* **model_info.schema.json**
|
||||
|
||||
Documentation at https://help.keyman.com/developer/cloud/model_info
|
||||
|
||||
* Primary version:
|
||||
* https://github.com/keymanapp/api.keyman.com/tree/master/schemas/model_info
|
||||
* Synchronized copies at:
|
||||
* https://github.com/keymanapp/keyman/tree/master/common/schemas/model_info
|
||||
|
||||
# .model_info version history
|
||||
|
||||
## 2023-08-11 2.0 stable
|
||||
* Removed:
|
||||
`.links`
|
||||
`.related[].note`
|
||||
- Source .model_info files are no longer needed, so source vs distribution
|
||||
model_info distinction is removed
|
||||
49
common/schemas/model_info/model_info.schema.json
Normal file
49
common/schemas/model_info/model_info.schema.json
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/schema#",
|
||||
"$ref": "#/definitions/ModelInfo",
|
||||
|
||||
"definitions": {
|
||||
"ModelInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"authorName": { "type": "string" },
|
||||
"authorEmail": { "type": "string", "format": "email" },
|
||||
"description": { "type": "string" },
|
||||
"license": { "type": "string", "enum": ["mit"] },
|
||||
"languages": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
|
||||
"lastModifiedDate": { "type": "string", "format": "date-time" },
|
||||
"packageFilename": { "type": "string" },
|
||||
"packageFileSize": { "type": "number" },
|
||||
"jsFilename": { "type": "string" },
|
||||
"jsFileSize": { "type": "number" },
|
||||
"isRTL": { "type": "boolean" },
|
||||
"packageIncludes": { "type": "array", "items": { "type": "string", "enum": ["fonts"] }, "additionalItems": false },
|
||||
"version": { "type": "string" },
|
||||
"minKeymanVersion": { "type": "string", "pattern": "^\\d+\\.0$" },
|
||||
"helpLink": { "type": "string", "pattern": "^http(s)?://help\\.keyman(-staging)?\\.com(.localhost)?/model/" },
|
||||
"sourcePath": { "type": "string", "pattern": "^(release|experimental)/.+/.+$" },
|
||||
"related": { "type": "object", "patternProperties": {
|
||||
".": { "$ref": "#/definitions/ModelRelatedInfo" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"license", "languages"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
"ModelRelatedInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"deprecates": { "type": "boolean" },
|
||||
"deprecatedBy": { "type": "boolean" }
|
||||
},
|
||||
"required": [],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,18 @@
|
|||
(*
|
||||
Name: UserMessages
|
||||
Copyright: Copyright (C) SIL International.
|
||||
Documentation:
|
||||
Description:
|
||||
Documentation:
|
||||
Description:
|
||||
Create Date: 5 Jul 2012
|
||||
|
||||
Modified Date: 6 Nov 2015
|
||||
Authors: mcdurdin
|
||||
Related Files:
|
||||
Dependencies:
|
||||
Related Files:
|
||||
Dependencies:
|
||||
|
||||
Bugs:
|
||||
Todo:
|
||||
Notes:
|
||||
Bugs:
|
||||
Todo:
|
||||
Notes:
|
||||
History: 05 Jul 2012 - mcdurdin - I3390 - Font helper can crash when WM_FONTCHANGE received [CrashID:keyman.exe_8.0.350.0_2C53A3AE_EAccessViolation]
|
||||
04 Nov 2012 - mcdurdin - I3519 - V9.0 - Merge of I3390 - Font helper can crash when WM_FONTCHANGE received
|
||||
28 May 2014 - mcdurdin - I4242 - Crash when OSK closed/reopened without dismissing hint window [CrashID:keyman.exe_9.0.449.0_2C405C5D_EInvalidPointer]
|
||||
|
|
@ -57,9 +57,14 @@ const
|
|||
WM_USER_SYNTAXCOLOURCHANGE = WM_USER + 130;
|
||||
|
||||
const
|
||||
// UfrmOSKOnScreenKeyboard
|
||||
// UfrmPrintOSK
|
||||
WM_USER_PrintKeyboard = WM_USER + 140;
|
||||
|
||||
const
|
||||
// UfrmOSKOnScreenKeyboard
|
||||
// Receive notification of modifier key changes from keyman32.dll
|
||||
WM_KEYMAN_OSK_MODIFIER_EVENT = WM_USER + 141;
|
||||
|
||||
const
|
||||
// UfrmOSKFontHelper
|
||||
WM_USER_FontChange = WM_USER + 150; // I3390 // I3519
|
||||
|
|
|
|||
|
|
@ -322,7 +322,7 @@ on any key to edit it. There are a number of controls:
|
|||
|
||||
Red circle with an X
|
||||
: This button to the top right of the key will delete the key from the
|
||||
row; if it is the last key on the row, the entire row will be
|
||||
row; if it is the only remaining key on the row, the entire row will be
|
||||
deleted.
|
||||
|
||||
Green arrow with a +
|
||||
|
|
@ -649,4 +649,4 @@ web page.
|
|||
The Send to email function makes it easier to open the addresses on your
|
||||
mobile device without having to enter them manually.
|
||||
|
||||
Learn more about [testing on the web and mobile devices](../guides/test/).
|
||||
Learn more about [testing on the web and mobile devices](../guides/test/).
|
||||
|
|
|
|||
|
|
@ -31,5 +31,5 @@ builder_run_action clean rm -rf ./build/
|
|||
builder_run_action configure node_select_version_and_npm_ci
|
||||
builder_run_action build tsc --build
|
||||
builder_run_action api api-extractor run --local --verbose
|
||||
builder_run_action test typescript_run_eslint_mocha_tests 70
|
||||
builder_run_action test typescript_run_eslint_mocha_tests 75
|
||||
builder_run_action publish ci_publish_npm
|
||||
|
|
|
|||
|
|
@ -406,4 +406,8 @@ export class AnalyzeOskCharacterUse {
|
|||
return result.join(' ');
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public unitTestEndPoints = {
|
||||
loadPreviousMap: this.loadPreviousMap.bind(this)
|
||||
};
|
||||
}
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*/
|
||||
// import { assert } from 'chai';
|
||||
import { assert } from 'chai';
|
||||
import 'mocha';
|
||||
import { AnalyzerMessages } from '../src/analyzer-messages.js';
|
||||
import { TestCompilerCallbacks, verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers';
|
||||
import { CompilerErrorNamespace } from '@keymanapp/developer-utils';
|
||||
import { AnalyzeOskCharacterUse } from '../src/osk-character-use/index.js';
|
||||
|
||||
|
||||
describe('AnalyzerMessages', function () {
|
||||
|
||||
|
|
@ -25,5 +27,19 @@ describe('AnalyzerMessages', function () {
|
|||
return verifyCompilerMessagesObject(AnalyzerMessages, CompilerErrorNamespace.Analyzer);
|
||||
});
|
||||
|
||||
// TODO: test each message
|
||||
it('should generate Warn_PreviousMapFileCouldNotBeLoaded when previous map file is missing or invalid', function () {
|
||||
const analyzer = new AnalyzeOskCharacterUse(callbacks);
|
||||
|
||||
const invalidFile = 'nonexistent-file.json';
|
||||
|
||||
assert.isNull(
|
||||
analyzer.unitTestEndPoints.loadPreviousMap(invalidFile),
|
||||
'Expected null when map file cannot be loaded'
|
||||
);
|
||||
|
||||
assert.isTrue(
|
||||
callbacks.hasMessage(AnalyzerMessages.WARN_PreviousMapFileCouldNotBeLoaded),
|
||||
'Expected warning when previous map file could not be loaded');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -270,7 +270,7 @@ export function WriteCompiledKeyboard(
|
|||
// Write the groups out
|
||||
|
||||
// I853 - begin unicode missing causes crash
|
||||
if (keyboard.startGroup.unicode == 0xFFFFFFFF) {
|
||||
if (keyboard.startGroup.unicode == -1 || keyboard.startGroup.unicode == 0xFFFFFFFF) {
|
||||
callbacks.reportMessage(KmwCompilerMessages.Error_InvalidBegin());
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
13
developer/src/kmc-kmn/test/fixtures/invalid-keyboards/ansi.kmn
vendored
Normal file
13
developer/src/kmc-kmn/test/fixtures/invalid-keyboards/ansi.kmn
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
c
|
||||
c test for ERROR_InvalidBegin in kmw-compiler ('begin Unicode' is required)
|
||||
c
|
||||
|
||||
store(&version) '9.0'
|
||||
store(&name) 'Test ANSI'
|
||||
store(&targets) 'web'
|
||||
|
||||
begin ANSI > use(main)
|
||||
|
||||
group(main) using keys
|
||||
|
||||
+ 'a' > 'aah'
|
||||
|
|
@ -45,6 +45,12 @@ describe('KmwCompilerMessages', function () {
|
|||
await testForMessage(this, ['kmw', 'validate_gesture.kmn'], KmwCompilerMessages.HINT_TouchLayoutUsesUnsupportedGesturesDownlevel);
|
||||
});
|
||||
|
||||
// ERROR_InvalidBegin (from KmnCompilerMessages, but in KeymanWeb compiler context)
|
||||
|
||||
it('should generate ERROR_InvalidBegin if the keyboard has no "begin unicode" statement', async function() {
|
||||
await testForMessage(this, ['invalid-keyboards', 'ansi.kmn'], KmwCompilerMessages.ERROR_InvalidBegin);
|
||||
});
|
||||
|
||||
// TODO: other messages
|
||||
|
||||
// WARN_ExtendedShiftFlagsNotSupportedInKeymanWeb:
|
||||
|
|
|
|||
|
|
@ -53,8 +53,8 @@ define("language.keyman", ["require", "exports"], function (require, exports) {
|
|||
[/\[/, { token: 'tag', bracket: '@open', next: '@virtualKey' } ],
|
||||
|
||||
// strings
|
||||
[/"([^"]|\\.)*$/, 'string.invalid' ], // non-teminated string
|
||||
[/'([^']|\\.)*$/, 'string.invalid' ], // non-teminated string
|
||||
[/"([^"])*$/, 'string.invalid' ], // non-teminated string
|
||||
[/'([^'])*$/, 'string.invalid' ], // non-teminated string
|
||||
[/"/, { token: 'string.quote', bracket: '@open', next: '@stringDouble' } ],
|
||||
[/'/, { token: 'string.quote', bracket: '@open', next: '@stringSingle' } ],
|
||||
],
|
||||
|
|
|
|||
|
|
@ -4,36 +4,49 @@ Code for `km-config` is in [linux/keyman-config](../../linux/keyman-config/).
|
|||
|
||||
## Preparing to run
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
If you are running from the repo or installing keyman-config manually rather than from a package
|
||||
then you will need to:
|
||||
|
||||
```bash
|
||||
sudo apt install python3-lxml python3-magic python3-numpy python3-qrcode python3-pil \
|
||||
python3-requests python3-requests-cache python3 python3-gi gir1.2-webkit2-4.1 dconf-cli \
|
||||
python3-setuptools python3-pip python3-dbus ibus libglib2.0-bin liblocale-gettext-perl
|
||||
python3-setuptools python3-pip python3-dbus ibus libglib2.0-bin liblocale-gettext-perl \
|
||||
python3-build python3-fonttools python3-sentry-sdk
|
||||
```
|
||||
|
||||
Either `python3-raven` or `python3-sentry-sdk` (>= 1.4) is required as well.
|
||||
On Ubuntu 22.04 and later run:
|
||||
Newer Ubuntu versions (>= 24.04) don't allow to modify the environment and directly
|
||||
install non-Debian-packaged Python packages. Instead you'll have to
|
||||
run the commands in a virtual environment.
|
||||
|
||||
This requires the installation of some additional dependencies:
|
||||
|
||||
```bash
|
||||
sudo apt install python3-sentry-sdk
|
||||
sudo apt install python3-venv libglib2.0-dev libdbus-1-dev pkg-config
|
||||
libcairo2-dev libgirepository-2.0-dev
|
||||
```
|
||||
|
||||
To install it on Ubuntu 18.04 and earlier run:
|
||||
Then create the virtual environment with `python3 -m venv km-venv` and activate
|
||||
it with `source km-venv/bin/activate`.
|
||||
|
||||
```bash
|
||||
sudo apt install python3-raven
|
||||
```
|
||||
### Prepare code
|
||||
|
||||
For Ubuntu versions that don't provide `python3-raven` but instead provide
|
||||
`python3-sentry-sdk` in a too old version (i.e. Ubuntu 20.04):
|
||||
Install `python3-sentry-sdk` from `packages.sil.org`,
|
||||
or install it with pip:
|
||||
Run `linux/keyman-config/build.sh build` to prepare the code.
|
||||
|
||||
```bash
|
||||
pip3 install sentry-sdk
|
||||
```
|
||||
### Installation
|
||||
|
||||
You can install the files with `linux/keyman-config/build.sh install`.
|
||||
This will install the files into `/usr/local`.
|
||||
|
||||
To uninstall you can run `linux/keyman-config/build.sh uninstall`.
|
||||
|
||||
## Manually installing
|
||||
|
||||
If you prefer to manually copy the files you'll still have to run the
|
||||
build step above. In addition you'll have to copy the GSettings schema:
|
||||
|
||||
### GSettings schema
|
||||
|
||||
Copy and compile the GSettings schema:
|
||||
|
||||
|
|
@ -48,15 +61,6 @@ Running `km-config` requires a language tag mapping file
|
|||
`keyman_config/standards/lang_tags_map.py`. This file gets generated during a package
|
||||
build, and also when running `build.sh build`.
|
||||
|
||||
## Installing manually from the repo
|
||||
|
||||
`linux/keyman-config/build.sh build && sudo linux/keyman-config/build.sh install`
|
||||
will install locally to `/usr/local`.
|
||||
|
||||
`pip3 help install` will give you more install options.
|
||||
|
||||
To uninstall you can run `sudo linux/keyman-config/build.sh uninstall`.
|
||||
|
||||
## Things to run from the command line
|
||||
|
||||
### km-config
|
||||
|
|
@ -119,15 +123,17 @@ Secondary-click gives you a menu including 'Back' to go back a page.
|
|||
|
||||
### km-kvk2ldml
|
||||
|
||||
`km-kvk2ldml [-p] [-k] [-o LDMLFILE] <kvk file>` Convert a Keyman kvk on-screen keyboard file to an LDML file. Optionally print the details of the kvk file (`-p`) optionally with all keys (`-k`).
|
||||
`km-kvk2ldml [-p] [-k] [-o LDMLFILE] <kvk file>` Convert a Keyman kvk on-screen
|
||||
keyboard file to an LDML file. Optionally print the details of the kvk file
|
||||
(`-p`) optionally with all keys (`-k`).
|
||||
|
||||
This command is run automatically when installing a keyboard package to create
|
||||
the necessary files for the onboard onscreen keyboard.
|
||||
|
||||
## Building the Debian package
|
||||
|
||||
You will need the build dependencies as well as the runtime dependencies above
|
||||
|
||||
`sudo apt install dh-python python3-all debhelper help2man`
|
||||
|
||||
Run `make deb`. This will build the Debian package in the `make_deb` directory.
|
||||
See the [packaging.md](https://github.com/keymanapp/keyman/blob/master/docs/linux/packaging.md#L26)
|
||||
document.
|
||||
|
||||
## Internationalization
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ We use different channels to build and distribute the Linux packages:
|
|||
- Launchpad repo for [stable](https://launchpad.net/~keymanapp/+archive/ubuntu/keyman),
|
||||
[beta](https://launchpad.net/~keymanapp/+archive/ubuntu/keyman-beta) and
|
||||
[alpha](https://launchpad.net/~keymanapp/+archive/ubuntu/keyman-alpha) versions
|
||||
- [pso](http://packages.sil.org/) and [llso](http://linux.lsdev.sil.org/ubuntu/)
|
||||
for stable, beta, and alpha versions
|
||||
- [pso](http://packages.sil.org/) for stable versions
|
||||
- [llso](http://linux.lsdev.sil.org/ubuntu/) for stable, beta, and alpha versions
|
||||
- artifacts on [GitHub](https://github.com/keymanapp/keyman/actions/workflows/deb-packaging.yml)
|
||||
for pull requests
|
||||
|
||||
|
|
@ -18,6 +18,84 @@ intended as a staging environment for testing. Packages on [pso](http://packages
|
|||
are uploaded manually after the packages received some testing. End users usually have only
|
||||
pso enabled.
|
||||
|
||||
## Building packages locally
|
||||
|
||||
It is possible to build packages locally. This can be done either directly on the machine,
|
||||
or in a Docker container which is more similar to the CI environment.
|
||||
|
||||
### Building packages locally directly on the machine
|
||||
|
||||
To build packages locally, you'll have to have the dependencies installed.
|
||||
|
||||
The easiest way to achieve this is to use the `mk-build-deps` tool:
|
||||
|
||||
```bash
|
||||
sudo apt install dh-python python3-all debhelper help2man devscripts \
|
||||
equivs jq quilt
|
||||
sudo mk-build-deps --install linux/debian/control
|
||||
```
|
||||
|
||||
Then you can build the Debian package with:
|
||||
|
||||
```bash
|
||||
KEYMAN_TIER=$(cat TIER.md)
|
||||
export KEYMAN_TIER
|
||||
cd linux
|
||||
./scripts/deb-packaging.sh source
|
||||
mkdir -p make_deb
|
||||
cd make_deb
|
||||
dpkg-source --extract ../../keyman_*.dsc
|
||||
cd keyman-*
|
||||
dch -v$(cat VERSION.md)-1 ""
|
||||
debuild -us -uc
|
||||
```
|
||||
|
||||
### Local package builds with Docker
|
||||
|
||||
It is possible to use the usual Debian/Ubuntu tools to create the package locally.
|
||||
For someone who only occasionally deals with packaging it might be easier to use
|
||||
Docker and the scripts that run on GitHub actions:
|
||||
|
||||
#### Prerequisites for local package builds with Docker
|
||||
|
||||
You'll have to create the docker image.
|
||||
|
||||
- clone [gha-ubuntu-packaging](https://github.com/sillsdev/gha-ubuntu-packaging)
|
||||
repo
|
||||
- create the image:
|
||||
|
||||
```bash
|
||||
cd /path/to/gha-ubuntu-packaging
|
||||
docker build --build-arg DIST=jammy --build-arg PLATFORM=amd64 -t sillsdev/jammy .
|
||||
```
|
||||
|
||||
#### Building packages with Docker
|
||||
|
||||
- create the source package
|
||||
|
||||
```bash
|
||||
cd $KEYMAN_ROOT
|
||||
KEYMAN_TIER=$(cat TIER.md)
|
||||
export KEYMAN_TIER
|
||||
cd linux
|
||||
./scripts/deb-packaging.sh source
|
||||
```
|
||||
|
||||
This will create the source package in $KEYMAN_ROOT directory.
|
||||
|
||||
- Create the binary packages with Docker:
|
||||
|
||||
```bash
|
||||
cd $KEYMAN_ROOT
|
||||
DIST=jammy
|
||||
docker run -v $(pwd):/source -i -t -w /source --platform=amd64 \
|
||||
--env INPUT_DIST=$DIST --env INPUT_SOURCE_DIR=. \
|
||||
--env INPUT_SOURCEPACKAGE=$(ls keyman_*.dsc | sort | tail -1) \
|
||||
sillsdev/$DIST
|
||||
```
|
||||
|
||||
This will create the binary packages in `$KEYMAN_ROOT/artifacts`.
|
||||
|
||||
## Package builds
|
||||
|
||||
Package builds happen on [Launchpad](#package-builds-on-launchpad) and
|
||||
|
|
@ -73,52 +151,6 @@ and of course the source code for the packages:
|
|||
to the main section `jammy`)
|
||||
- if the build is successful the job archives the artifacts
|
||||
|
||||
### Local package builds with Docker
|
||||
|
||||
It is possible to use the usual Debian/Ubuntu tools to create the package locally.
|
||||
For someone who only occasionally deals with packaging it might be easier to use
|
||||
Docker and the scripts that run on GitHub actions:
|
||||
|
||||
#### Prerequisites for local package builds with Docker
|
||||
|
||||
You'll have to create the docker image.
|
||||
|
||||
- clone [gha-ubuntu-packaging](https://github.com/sillsdev/gha-ubuntu-packaging)
|
||||
repo
|
||||
- create the image:
|
||||
|
||||
```bash
|
||||
cd /path/to/gha-ubuntu-packaging
|
||||
docker build --build-arg DIST=jammy --build-arg PLATFORM=amd64 -t sillsdev/jammy .
|
||||
```
|
||||
|
||||
#### Building packages with Docker
|
||||
|
||||
- create the source package
|
||||
|
||||
```bash
|
||||
cd $KEYMAN_ROOT
|
||||
KEYMAN_TIER=$(cat TIER.md)
|
||||
export KEYMAN_TIER
|
||||
cd linux
|
||||
./scripts/deb-packaging.sh source
|
||||
```
|
||||
|
||||
This will create the source package in $KEYMAN_ROOT directory.
|
||||
|
||||
- Create the binary packages with Docker:
|
||||
|
||||
```bash
|
||||
cd $KEYMAN_ROOT
|
||||
DIST=jammy
|
||||
docker run -v $(pwd):/source -i -t -w /source --platform=amd64 \
|
||||
--env INPUT_DIST=$DIST --env INPUT_SOURCE_DIR=. \
|
||||
--env INPUT_SOURCEPACKAGE=$(ls keyman_*.dsc | sort | tail -1) \
|
||||
sillsdev/$DIST
|
||||
```
|
||||
|
||||
This will create the binary packages in `$KEYMAN_ROOT/artifacts`.
|
||||
|
||||
## Package builds on Launchpad
|
||||
|
||||
Package builds on Launchpad are triggered manually by running the Keyman script
|
||||
|
|
|
|||
2
linux/debian/source/include-binaries
Normal file
2
linux/debian/source/include-binaries
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
debian/tests/data/alephwithbeth.kmp
|
||||
debian/tests/data/khmer_angkor.kmp
|
||||
|
|
@ -24,11 +24,40 @@ lowercase ASCII letters, plus or minus signs, dots or slashes.
|
|||
For example:
|
||||
|
||||
```bash
|
||||
autopkgtest-buildvm-ubuntu-cloud -v --release=jammy
|
||||
autopkgtest-buildvm-ubuntu-cloud -v --release=noble
|
||||
```
|
||||
|
||||
- Run tests
|
||||
|
||||
```bash
|
||||
autopkgtest -B *.deb keyman_*.dsc -- qemu autopkgtest-jammy-amd64.img
|
||||
autopkgtest --apt-upgrade --no-built-binaries *.deb keyman_*.dsc -- qemu autopkgtest-noble-amd64.img
|
||||
```
|
||||
|
||||
## Developing tests
|
||||
|
||||
While developing tests, it is possible to run the tests directly from
|
||||
the source tree:
|
||||
|
||||
```bash
|
||||
cd linux
|
||||
autopkgtest --apt-upgrade --no-built-binaries ../artifacts/*.deb . -- qemu /path/to/autopkgtest-noble-amd64.img
|
||||
```
|
||||
|
||||
This requires packages to be available in `${KEYMAN_ROOT}/artifacts`.
|
||||
These can be built from source with:
|
||||
|
||||
```bash
|
||||
cd linux
|
||||
rm -rf debian/patches/ keyman-config/keyman_config/.pc
|
||||
scripts/deb-packaging.sh source
|
||||
DIST=noble
|
||||
cd ..
|
||||
docker run -v $(pwd):/source -i -t -w /source --platform=amd64 \
|
||||
--env INPUT_DIST=$DIST --env INPUT_SOURCE_DIR=. \
|
||||
--env INPUT_SOURCEPACKAGE=$(ls keyman_*.dsc | sort | tail -1) \
|
||||
sillsdev/$DIST
|
||||
```
|
||||
|
||||
New packages have to be built for changes outside of `linux/debian`.
|
||||
Changes in files in `linux/debian` are automatically considered when
|
||||
running `autopkgtest`.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
Tests: test-build
|
||||
Tests: test-build test-gnome-cli test-ibus-cli
|
||||
Depends: @,
|
||||
build-essential,
|
||||
pkg-config,
|
||||
|
|
|
|||
BIN
linux/debian/tests/data/alephwithbeth.kmp
Normal file
BIN
linux/debian/tests/data/alephwithbeth.kmp
Normal file
Binary file not shown.
BIN
linux/debian/tests/data/khmer_angkor.kmp
Normal file
BIN
linux/debian/tests/data/khmer_angkor.kmp
Normal file
Binary file not shown.
100
linux/debian/tests/test-cli.inc.sh
Normal file
100
linux/debian/tests/test-cli.inc.sh
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# shellcheck shell=bash
|
||||
|
||||
verify_ibus_restarted() {
|
||||
local ibus_count new_pid
|
||||
|
||||
ibus_count=$(pgrep --count ibus-daemon)
|
||||
if ! (( ibus_count == 1 )); then
|
||||
echo "FAIL ${TEST_SCRIPT}/${FUNCNAME[1]}: ibus-daemon not running"
|
||||
exit 1
|
||||
fi
|
||||
new_pid=$(pgrep ibus-daemon)
|
||||
if [[ "${new_pid}" == "${ibus_pid}" ]]; then
|
||||
echo "FAIL ${TEST_SCRIPT}/${FUNCNAME[1]}: ibus-daemon was not restarted"
|
||||
exit 1
|
||||
fi
|
||||
ibus_pid="${new_pid}"
|
||||
}
|
||||
|
||||
verify_gnome_input_sources() {
|
||||
local expected_sources="$1"
|
||||
local sources
|
||||
|
||||
sources=$(gsettings get org.gnome.desktop.input-sources sources)
|
||||
if [[ "${sources}" != "${expected_sources}" ]]; then
|
||||
echo "FAIL ${TEST_SCRIPT}/${FUNCNAME[1]}: input-sources wrong. Got ${sources} instead of ${expected_sources}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
verify_ibus_preload_engines() {
|
||||
local expected="$1"
|
||||
local sources
|
||||
|
||||
sources=$(gsettings get org.freedesktop.ibus.general preload-engines)
|
||||
if [[ "${sources}" != "${expected}" ]]; then
|
||||
echo "FAIL ${TEST_SCRIPT}/${FUNCNAME[1]}: preload-engines wrong. Got ${sources} instead of ${expected}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
verify_custom_keyboards() {
|
||||
local expected_custom_keyboards="$1"
|
||||
local custom_keyboards
|
||||
|
||||
custom_keyboards=$(gsettings get com.keyman.engine additional-keyboards)
|
||||
if [[ "${custom_keyboards}" != "${expected_custom_keyboards}" ]]; then
|
||||
echo "FAIL ${TEST_SCRIPT}/${FUNCNAME[1]}: custom keyboards wrong. Got ${custom_keyboards} instead of ${expected_custom_keyboards}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
verify_keyboard_file_exists() {
|
||||
local expected_keyboard_file="$1"
|
||||
|
||||
if [[ ! -f "${expected_keyboard_file}" ]]; then
|
||||
echo "FAIL ${TEST_SCRIPT}/${FUNCNAME[1]}: keyboard file ${expected_keyboard_file} not found"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
verify_keyboard_file_not_exists() {
|
||||
local expected_keyboard_file="$1"
|
||||
|
||||
if [[ -f "${expected_keyboard_file}" ]]; then
|
||||
echo "FAIL ${FUNCNAME[1]}: keyboard file ${expected_keyboard_file} still exists"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
verify_no_error() {
|
||||
local errocode="$1"
|
||||
|
||||
if [[ "${errocode}" != "0" ]]; then
|
||||
echo "FAIL ${TEST_SCRIPT}/${FUNCNAME[1]}: non-zero exit code ${errocode}."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
run_tests() {
|
||||
export SLEEP_TIME=5
|
||||
export TEST_SCRIPT
|
||||
TEST_SCRIPT="$(basename "${BASH_SOURCE[${#BASH_SOURCE[@]}-1]}")"
|
||||
|
||||
gsettings reset com.keyman.engine additional-keyboards
|
||||
|
||||
echo "Keyman Version: $(km-config --version)"
|
||||
if ! pgrep --uid "$(id -u)" ibus-daemon > /dev/null; then
|
||||
echo "Starting ibus"
|
||||
ibus start -d
|
||||
sleep "${SLEEP_TIME}"
|
||||
else
|
||||
echo "ibus-daemon already running"
|
||||
fi
|
||||
|
||||
km_package_install__user
|
||||
km_package_install__shared
|
||||
km_package_list_installed
|
||||
km_package_uninstall__user
|
||||
km_package_uninstall__shared
|
||||
}
|
||||
104
linux/debian/tests/test-gnome-cli
Executable file
104
linux/debian/tests/test-gnome-cli
Executable file
|
|
@ -0,0 +1,104 @@
|
|||
#!/bin/bash
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
. "$(dirname "$0")/test-cli.inc.sh"
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# gnome-shell tests
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
km_package_install__user() {
|
||||
# Setup
|
||||
gsettings set org.gnome.desktop.input-sources sources "[('xkb', 'us')]"
|
||||
|
||||
# Run
|
||||
km-package-install --file "$(dirname "$0")/data/khmer_angkor.kmp"
|
||||
|
||||
# Verify
|
||||
verify_no_error $?
|
||||
verify_ibus_restarted
|
||||
verify_keyboard_file_exists "${HOME}/.local/share/keyman/khmer_angkor/khmer_angkor.kmx"
|
||||
verify_gnome_input_sources "[('xkb', 'us'), ('ibus', 'km:${HOME}/.local/share/keyman/khmer_angkor/khmer_angkor.kmx')]"
|
||||
verify_custom_keyboards "@as []"
|
||||
|
||||
echo "PASS ${TEST_SCRIPT}/${FUNCNAME[0]}"
|
||||
}
|
||||
|
||||
km_package_install__shared() {
|
||||
# Setup - continue from previous tests
|
||||
|
||||
# Run
|
||||
sudo km-package-install --shared --bcp47 ady --file "$(dirname "$0")/data/alephwithbeth.kmp"
|
||||
|
||||
# Verify
|
||||
verify_no_error $?
|
||||
verify_ibus_restarted
|
||||
verify_keyboard_file_exists "/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx"
|
||||
verify_gnome_input_sources "[('xkb', 'us'), ('ibus', 'km:${HOME}/.local/share/keyman/khmer_angkor/khmer_angkor.kmx'), ('ibus', 'ady:/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx')]"
|
||||
verify_custom_keyboards "['ady:/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx']"
|
||||
|
||||
echo "PASS ${TEST_SCRIPT}/${FUNCNAME[0]}"
|
||||
}
|
||||
|
||||
km_package_list_installed() {
|
||||
# Setup - continue from previous tests
|
||||
local result expected
|
||||
|
||||
# Run
|
||||
result=$(km-package-list-installed)
|
||||
|
||||
# Verify
|
||||
verify_no_error $?
|
||||
expected="--- Installed user Keyman keyboard packages ---
|
||||
Khmer Angkor, version: 1.5, id: khmer_angkor
|
||||
--- Installed shared Keyman keyboard packages ---
|
||||
Aleph With Beth, version: 1.0.6, id: alephwithbeth
|
||||
--- Installed OS Keyman keyboard packages ---"
|
||||
if [[ "${result}" != "${expected}" ]]; then
|
||||
echo "FAIL ${TEST_SCRIPT}/${FUNCNAME[0]}: result wrong."
|
||||
echo "EXPECTED:
|
||||
${expected}"
|
||||
echo "ACTUAL:
|
||||
${result}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "PASS ${FUNCNAME[0]}"
|
||||
}
|
||||
|
||||
km_package_uninstall__user() {
|
||||
# Setup - continue from previous tests
|
||||
|
||||
# Run
|
||||
km-package-uninstall khmer_angkor
|
||||
|
||||
# Verify
|
||||
verify_no_error $?
|
||||
verify_keyboard_file_not_exists "${HOME}/.local/share/keyman/khmer_angkor/khmer_angkor.kmx"
|
||||
verify_gnome_input_sources "[('xkb', 'us'), ('ibus', 'ady:/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx')]"
|
||||
verify_custom_keyboards "['ady:/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx']"
|
||||
|
||||
echo "PASS ${TEST_SCRIPT}/${FUNCNAME[0]}"
|
||||
}
|
||||
|
||||
km_package_uninstall__shared() {
|
||||
# Setup - continue from previous tests
|
||||
|
||||
# Run
|
||||
sudo km-package-uninstall --shared alephwithbeth
|
||||
|
||||
# Verify
|
||||
verify_no_error $?
|
||||
verify_keyboard_file_not_exists "/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx"
|
||||
verify_gnome_input_sources "[('xkb', 'us')]"
|
||||
verify_custom_keyboards "@as []"
|
||||
|
||||
echo "PASS ${TEST_SCRIPT}/${FUNCNAME[0]}"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
# Simulate running gnome-shell
|
||||
export XDG_CURRENT_DESKTOP=gnome
|
||||
|
||||
run_tests
|
||||
97
linux/debian/tests/test-ibus-cli
Executable file
97
linux/debian/tests/test-ibus-cli
Executable file
|
|
@ -0,0 +1,97 @@
|
|||
#!/bin/bash
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
. "$(dirname "$0")/test-cli.inc.sh"
|
||||
|
||||
km_package_install__user() {
|
||||
# Setup
|
||||
gsettings set org.freedesktop.ibus.general preload-engines "['xkb:us::eng']"
|
||||
|
||||
# Run
|
||||
km-package-install --file "$(dirname "$0")/data/khmer_angkor.kmp"
|
||||
|
||||
# Verify
|
||||
verify_no_error $?
|
||||
verify_ibus_restarted
|
||||
verify_keyboard_file_exists "${HOME}/.local/share/keyman/khmer_angkor/khmer_angkor.kmx"
|
||||
verify_ibus_preload_engines "['xkb:us::eng', 'km:${HOME}/.local/share/keyman/khmer_angkor/khmer_angkor.kmx']"
|
||||
verify_custom_keyboards "@as []"
|
||||
|
||||
echo "PASS ${TEST_SCRIPT}/${FUNCNAME[0]}"
|
||||
}
|
||||
|
||||
km_package_install__shared() {
|
||||
# Setup - continue from previous tests
|
||||
|
||||
# Run
|
||||
sudo km-package-install --shared --bcp47 ady --file "$(dirname "$0")/data/alephwithbeth.kmp"
|
||||
|
||||
# Verify
|
||||
verify_no_error $?
|
||||
verify_ibus_restarted
|
||||
verify_keyboard_file_exists "/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx"
|
||||
verify_ibus_preload_engines "['xkb:us::eng', 'km:${HOME}/.local/share/keyman/khmer_angkor/khmer_angkor.kmx', 'ady:/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx']"
|
||||
verify_custom_keyboards "['ady:/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx']"
|
||||
|
||||
echo "PASS ${TEST_SCRIPT}/${FUNCNAME[0]}"
|
||||
}
|
||||
|
||||
km_package_list_installed() {
|
||||
# Setup - continue from previous tests
|
||||
local result expected
|
||||
|
||||
# Run
|
||||
result=$(km-package-list-installed)
|
||||
|
||||
# Verify
|
||||
verify_no_error $?
|
||||
expected="--- Installed user Keyman keyboard packages ---
|
||||
Khmer Angkor, version: 1.5, id: khmer_angkor
|
||||
--- Installed shared Keyman keyboard packages ---
|
||||
Aleph With Beth, version: 1.0.6, id: alephwithbeth
|
||||
--- Installed OS Keyman keyboard packages ---"
|
||||
if [[ "${result}" != "${expected}" ]]; then
|
||||
echo "FAIL ${TEST_SCRIPT}/${FUNCNAME[0]}: result wrong."
|
||||
echo "EXPECTED:
|
||||
${expected}"
|
||||
echo "ACTUAL:
|
||||
${result}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "PASS ${TEST_SCRIPT}/${FUNCNAME[0]}"
|
||||
}
|
||||
|
||||
km_package_uninstall__user() {
|
||||
# Setup - continue from previous tests
|
||||
|
||||
# Run
|
||||
km-package-uninstall khmer_angkor
|
||||
|
||||
# Verify
|
||||
verify_no_error $?
|
||||
verify_keyboard_file_not_exists "${HOME}/.local/share/keyman/khmer_angkor/khmer_angkor.kmx"
|
||||
verify_ibus_preload_engines "['xkb:us::eng', 'ady:/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx']"
|
||||
verify_custom_keyboards "['ady:/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx']"
|
||||
|
||||
echo "PASS ${TEST_SCRIPT}/${FUNCNAME[0]}"
|
||||
}
|
||||
|
||||
km_package_uninstall__shared() {
|
||||
# Setup - continue from previous tests
|
||||
|
||||
# Run
|
||||
sudo km-package-uninstall --shared alephwithbeth
|
||||
|
||||
# Verify
|
||||
verify_no_error $?
|
||||
verify_keyboard_file_not_exists "/usr/local/share/keyman/alephwithbeth/alephwithbeth.kmx"
|
||||
verify_ibus_preload_engines "['xkb:us::eng']"
|
||||
verify_custom_keyboards "@as []"
|
||||
|
||||
echo "PASS ${TEST_SCRIPT}/${FUNCNAME[0]}"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
run_tests
|
||||
|
|
@ -1,47 +1,13 @@
|
|||
#!/usr/bin/make
|
||||
|
||||
default:
|
||||
./build.sh clean build
|
||||
|
||||
install:
|
||||
./build.sh install
|
||||
|
||||
uninstall: # run as sudo
|
||||
./build.sh uninstall
|
||||
|
||||
clean:
|
||||
./build.sh clean
|
||||
|
||||
check:
|
||||
./build.sh test
|
||||
|
||||
dist: clean version
|
||||
# TODO: remove this if when we no longer support Ubuntu 22.04 Jammy
|
||||
if dpkg --compare-versions "$(lsb_release -r -s)" lt 24.04; then \
|
||||
python3 setup.py sdist ; \
|
||||
else \
|
||||
python3 -m build --outdir build --sdist ; \
|
||||
fi
|
||||
|
||||
deb: dist
|
||||
@KEYMAN_VERSION=$(shell echo `basename dist/*.gz .tar.gz|cut -d "-" -f2` > /tmp/keyman_version)
|
||||
@echo KEYMAN_VERSION is $(shell cat /tmp/keyman_version)
|
||||
@mkdir -p make_deb
|
||||
cd make_deb && tar xf ../dist/keyman_config-$(shell cat /tmp/keyman_version).tar.gz && \
|
||||
mv keyman_config-$(shell cat /tmp/keyman_version) keyman-config-$(shell cat /tmp/keyman_version) && \
|
||||
tar cfz keyman-config_$(shell cat /tmp/keyman_version).orig.tar.gz keyman-config-$(shell cat /tmp/keyman_version) && \
|
||||
cd keyman-config-$(shell cat /tmp/keyman_version) && cp -a ../../../debian . && dch -v$(shell cat /tmp/keyman_version)-1 ""
|
||||
cd make_deb/keyman-config-$(shell cat /tmp/keyman_version) && debuild -us -uc
|
||||
rm /tmp/keyman_version
|
||||
|
||||
man:
|
||||
./build-help.sh --man --no-reconf
|
||||
echo "Use ./build.sh command instead of make!"
|
||||
|
||||
version_reconf:
|
||||
cd .. && ./scripts/reconf.sh
|
||||
|
||||
version: version_reconf
|
||||
$(eval KEYMAN_VERSION := $(shell python3 -c "from keyman_config import __releaseversion__; print(__releaseversion__)"))
|
||||
$(eval KEYMAN_VERSION := $(shell ./build.sh build && python3 -c "from keyman_config import __releaseversion__; print(__releaseversion__)"))
|
||||
|
||||
# i18n
|
||||
|
||||
|
|
|
|||
|
|
@ -39,18 +39,25 @@ clean_action() {
|
|||
fi
|
||||
}
|
||||
|
||||
install_schema() {
|
||||
local SCHEMA_DIR="$1"
|
||||
mkdir -p "${SCHEMA_DIR}"
|
||||
cp resources/com.keyman.gschema.xml "${SCHEMA_DIR}"/
|
||||
glib-compile-schemas "${SCHEMA_DIR}"
|
||||
}
|
||||
|
||||
execute_with_temp_schema() {
|
||||
local TEMP_DATA_DIR SCHEMA_DIR
|
||||
TEMP_DATA_DIR=$(mktemp -d)
|
||||
SCHEMA_DIR="${TEMP_DATA_DIR}/glib-2.0/schemas"
|
||||
export XDG_DATA_DIRS="${TEMP_DATA_DIR}":${XDG_DATA_DIRS-}
|
||||
export GSETTINGS_SCHEMA_DIR="${SCHEMA_DIR}:/usr/share/glib-2.0/schemas/:${GSETTINGS_SCHEMA_DIR-}"
|
||||
mkdir -p "${SCHEMA_DIR}"
|
||||
cp resources/com.keyman.gschema.xml "${SCHEMA_DIR}"/
|
||||
glib-compile-schemas "${SCHEMA_DIR}"
|
||||
|
||||
install_schema "${SCHEMA_DIR}"
|
||||
"$@"
|
||||
export XDG_DATA_DIRS=${XDG_DATA_DIRS#*:}
|
||||
|
||||
unset GSETTINGS_SCHEMA_DIR
|
||||
export XDG_DATA_DIRS=${XDG_DATA_DIRS#*:}
|
||||
rm -rf "${TEMP_DATA_DIR}"
|
||||
}
|
||||
|
||||
|
|
@ -105,25 +112,42 @@ test_action() {
|
|||
|
||||
if builder_has_option --report; then
|
||||
builder_echo "Creating coverage report"
|
||||
python3 -m coverage html --directory="$THIS_SCRIPT_PATH/build/coveragereport/" --data-file=build/.coverage
|
||||
python3 -m coverage html --directory="${THIS_SCRIPT_PATH}/build/coveragereport/" --data-file=build/.coverage
|
||||
fi
|
||||
}
|
||||
|
||||
install_action() {
|
||||
if [ -n "${SUDO_USER:-}" ]; then
|
||||
# with sudo install into /usr/local
|
||||
pip3 install qrcode sentry-sdk
|
||||
if [[ -v VIRTUAL_ENV ]]; then
|
||||
# Running in a virtual environment
|
||||
pip3 install qrcode sentry-sdk fonttools setuptools
|
||||
pip3 install .
|
||||
# install icons
|
||||
mkdir -p "${VIRTUAL_ENV}/share/keyman/icons"
|
||||
cp keyman_config/icons/* "${VIRTUAL_ENV}/share/keyman/icons"
|
||||
# install man pages
|
||||
mkdir -p "${VIRTUAL_ENV}/share/man/man1"
|
||||
cp ../../debian/man/*.1 "${VIRTUAL_ENV}/share/man/man1"
|
||||
# install schema
|
||||
install_schema "${VIRTUAL_ENV}/share/glib-2.0/schemas"
|
||||
elif [[ -n "${SUDO_USER:-}" ]] || [[ "$(id -u)" == "0" ]]; then
|
||||
# as root install into /usr/local
|
||||
pip3 install qrcode sentry-sdk
|
||||
pip3 install --prefix /usr/local .
|
||||
# install icons
|
||||
mkdir -p /usr/local/share/keyman/icons
|
||||
cp keyman_config/icons/* /usr/local/share/keyman/icons
|
||||
# install man pages
|
||||
mkdir -p /usr/local/share/man/man1
|
||||
cp ../../debian/man/*.1 /usr/local/share/man/man1
|
||||
# install schema
|
||||
install_schema "/usr/local/share/glib-2.0/schemas"
|
||||
else
|
||||
# without sudo install into /tmp/keyman (or $DESTDIR)
|
||||
mkdir -p "/tmp/keyman/$(python3 -c 'import sys;import os;pythonver="python%d.%d" % (sys.version_info[0], sys.version_info[1]);sitedir = os.path.join("lib", pythonver, "site-packages");print(sitedir)')"
|
||||
python_version=$(python3 -c 'import sys;import os;pythonver="python%d.%d" % (sys.version_info[0], sys.version_info[1]);sitedir = os.path.join("lib", pythonver, "site-packages");print(sitedir)')
|
||||
mkdir -p "/tmp/keyman/${python_version}"
|
||||
pip3 install --prefix /tmp/keyman .
|
||||
# install schema
|
||||
install_schema "/tmp/keyman/local/share/glib-2.0/schemas"
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -132,6 +156,7 @@ uninstall_action() {
|
|||
clean_action
|
||||
rm -rf /usr/local/share/keyman/icons
|
||||
rm -f /usr/local/share/man/man1/km-*.1
|
||||
rm -f /usr/local/share/glib-2.0/schemas/com.keyman.gschema.xml
|
||||
pip3 uninstall keyman_config
|
||||
rm -f /usr/local/bin/km-config
|
||||
rm -f /usr/local/bin/km-kvk2ldml
|
||||
|
|
|
|||
|
|
@ -111,6 +111,14 @@ def _set_dbus_started_for_session(value):
|
|||
|
||||
|
||||
def verify_dbus_running():
|
||||
if os.environ.get('SUDO_USER'):
|
||||
# If running with sudo we won't try to start dbus since it will
|
||||
# not always work. The main reason we need dbus is so that we can
|
||||
# notify other km-config processes that the keyboard list has changed.
|
||||
# If running with sudo we call dbus-send instead of instantiating the
|
||||
# KeymanConfigServiceManager class.
|
||||
return
|
||||
|
||||
if 'DBUS_SESSION_BUS_ADDRESS' in os.environ:
|
||||
# already running - nothing to do
|
||||
return
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import contextlib
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import dbus
|
||||
import dbus.bus
|
||||
|
|
@ -12,6 +13,9 @@ from dbus import DBusException
|
|||
|
||||
BUS_NAME = 'com.Keyman.Config'
|
||||
OBJECT_PATH = '/com/Keyman/Config'
|
||||
DBusSessionBusAddressValue = f'unix:path=/run/user/{os.environ.get("SUDO_UID")}/bus'
|
||||
DBusSessionBusAddress = f'DBUS_SESSION_BUS_ADDRESS={DBusSessionBusAddressValue}'
|
||||
XdgRuntimeDir = f'XDG_RUNTIME_DIR=/run/user/{os.environ.get("SUDO_UID")}'
|
||||
|
||||
|
||||
__keyman_config_service = None
|
||||
|
|
@ -36,6 +40,16 @@ class KeymanConfigService(dbus.service.Object):
|
|||
self.KeyboardListChangedSignal()
|
||||
|
||||
|
||||
def keyboard_list_changed():
|
||||
if os.environ.get('SUDO_USER'):
|
||||
args = ['sudo', '-u', os.environ.get('SUDO_USER'), 'dbus-send',
|
||||
f'--bus={DBusSessionBusAddressValue}',
|
||||
'/com/Keyman/Config', 'com.Keyman.Config.keyboard_list_changed']
|
||||
subprocess.run(args)
|
||||
else:
|
||||
get_keyman_config_service().keyboard_list_changed()
|
||||
|
||||
|
||||
class KeymanConfigServiceManager:
|
||||
def __init__(self, handler):
|
||||
self.__name_owner_watch = None
|
||||
|
|
|
|||
|
|
@ -22,7 +22,12 @@ class InstallLocation(GObject.GEnum):
|
|||
User = 3
|
||||
Unknown = 99
|
||||
|
||||
can_use_cache = True
|
||||
|
||||
# When running under sudo, we don't want to use the cache
|
||||
# since that stores files with root permissions so that they
|
||||
# can't be deleted/replaced by the user later.
|
||||
can_use_cache = not os.environ.get('SUDO_USER')
|
||||
|
||||
|
||||
def get_install_area_string(area):
|
||||
if area == InstallLocation.OS:
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import sys
|
|||
from gi.repository import Gio # needs to come before gi.overrides.GLib!
|
||||
from gi.overrides.GLib import Variant
|
||||
|
||||
from keyman_config.dbus_util import DBusSessionBusAddress, XdgRuntimeDir
|
||||
|
||||
# The xdg module got renamed to xdg_base_dirs, but the package
|
||||
# (python3-xdg) on Ubuntu still ships xdg. So we support both.
|
||||
try:
|
||||
|
|
@ -71,29 +73,19 @@ class GSettings():
|
|||
def get(self, key):
|
||||
if self.is_sudo:
|
||||
args = ['sudo', '-H', '-u', os.environ.get('SUDO_USER'),
|
||||
DBusSessionBusAddress, XdgRuntimeDir,
|
||||
'gsettings', 'get', self.schema_id, key]
|
||||
if sys.version_info.major <= 3 and sys.version_info.minor < 7:
|
||||
# capture_output got added in Python 3.7
|
||||
try:
|
||||
output = subprocess.check_output(args)
|
||||
value = eval(output)
|
||||
except subprocess.CalledProcessError:
|
||||
value = None
|
||||
logging.warning('Could not convert to sources')
|
||||
else:
|
||||
process = subprocess.run(args, capture_output=True)
|
||||
if process.returncode == 0:
|
||||
output = process.stdout
|
||||
if output.decode('utf-8').startswith('['):
|
||||
value = eval(output)
|
||||
return value
|
||||
process = subprocess.run(args, capture_output=True)
|
||||
if process.returncode == 0:
|
||||
output = process.stdout
|
||||
if output.decode('utf-8').startswith('['):
|
||||
return eval(output)
|
||||
|
||||
logging.debug('Could not convert to sources')
|
||||
return None
|
||||
logging.debug('Could not convert to sources')
|
||||
return None
|
||||
else:
|
||||
variant = self.schema.get_value(key)
|
||||
value = self._convert_variant_to_array(variant)
|
||||
return value
|
||||
return self._convert_variant_to_array(variant)
|
||||
|
||||
def _get_schema_path(self):
|
||||
return self.schema_id.replace('.', '/')
|
||||
|
|
@ -121,9 +113,10 @@ class GSettings():
|
|||
def set(self, key, value, type_string) -> None:
|
||||
if self.is_sudo:
|
||||
variant = str(value)
|
||||
subprocess.run(
|
||||
['sudo', '-H', '-u', os.environ.get('SUDO_USER'),
|
||||
'gsettings', 'set', self.schema_id, key, variant], check=False)
|
||||
args = ['sudo', '-H', '-u', os.environ.get('SUDO_USER'),
|
||||
DBusSessionBusAddress, XdgRuntimeDir,
|
||||
'gsettings', 'set', self.schema_id, key, variant]
|
||||
subprocess.run(args, check=False)
|
||||
elif get_dbus_started_for_session():
|
||||
self._set_key_file(key, value, type_string)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -10,9 +10,11 @@ import time
|
|||
|
||||
import gi
|
||||
|
||||
from keyman_config.dbus_util import DBusSessionBusAddress, XdgRuntimeDir
|
||||
|
||||
gi.require_version('IBus', '1.0')
|
||||
from gi.repository import IBus
|
||||
from pkg_resources import parse_version
|
||||
from packaging import version
|
||||
|
||||
from keyman_config.gnome_keyboards_util import is_gnome_desktop
|
||||
from keyman_config.gsettings import GSettings
|
||||
|
|
@ -114,10 +116,8 @@ def verify_ibus_daemon(start):
|
|||
return IbusDaemon.ERROR_USER
|
||||
|
||||
try:
|
||||
ps_output = subprocess.run(('ps', '--user', user, '-o', 's=', '-o', 'cmd'),
|
||||
check=False, stdout=subprocess.PIPE).stdout
|
||||
ibus_daemons = re.findall(r'^[^ZT] (/[^ ]+/|)ibus-daemon( .*|$)',
|
||||
ps_output.decode('utf-8'), re.MULTILINE)
|
||||
ibus_daemons = subprocess.run(('pgrep', '-u', user, 'ibus-daemon'),
|
||||
check=False, stdout=subprocess.PIPE).stdout.split()
|
||||
if len(ibus_daemons) <= 0:
|
||||
if start:
|
||||
_start_ibus_daemon(realuser)
|
||||
|
|
@ -156,7 +156,7 @@ def _get_ibus_version():
|
|||
|
||||
def _start_ibus_daemon(realuser):
|
||||
try:
|
||||
if parse_version(_get_ibus_version()) >= parse_version('1.5.28'):
|
||||
if version.parse(_get_ibus_version()) >= version.parse('1.5.28'):
|
||||
# IBus ~1.5.28 added the `start` command, so we use that if possible
|
||||
# and let IBus deal with the necessary parameters
|
||||
args = ['ibus', 'start', '-d']
|
||||
|
|
@ -171,36 +171,28 @@ def _start_ibus_daemon(realuser):
|
|||
|
||||
if realuser:
|
||||
# we have been called with `sudo`. Start ibus-daemon for the real user.
|
||||
logging.info('starting ibus-daemon for user %s', realuser)
|
||||
subprocess.run(['sudo', '-u', realuser].extend(args), check=True)
|
||||
args = ['sudo', '-u', realuser,
|
||||
DBusSessionBusAddress, XdgRuntimeDir, *args]
|
||||
logging.info(f'starting ibus-daemon for user {realuser} with args: {args}')
|
||||
else:
|
||||
logging.info('ibus-daemon not running. Starting it...')
|
||||
subprocess.run(args, check=True)
|
||||
except Exception:
|
||||
logging.warning('Failed to start ibus-daemon')
|
||||
logging.info(f'ibus-daemon not running. Starting it with args: {args}...')
|
||||
subprocess.run(args, check=True)
|
||||
logging.info('ibus-daemon started successfully')
|
||||
except Exception as e:
|
||||
logging.warning(f'Failed to start ibus-daemon: {e}')
|
||||
|
||||
|
||||
def restart_ibus(bus=None):
|
||||
verify_ibus_daemon(False)
|
||||
if realuser := os.environ.get('SUDO_USER'):
|
||||
# we have been called with `sudo`. Restart ibus for the real user.
|
||||
logging.info('restarting IBus by subprocess for user %s', realuser)
|
||||
subprocess.run(['sudo', '-u', realuser, 'ibus', 'restart'], check=False)
|
||||
logging.info(f'restarting IBus by subprocess for user {realuser}')
|
||||
subprocess.run(['sudo', '-u', realuser,
|
||||
DBusSessionBusAddress, XdgRuntimeDir,
|
||||
'ibus', 'restart'], check=False)
|
||||
else:
|
||||
logging.info('restarting IBus through API')
|
||||
try:
|
||||
if not bus:
|
||||
bus = get_ibus_bus()
|
||||
if bus:
|
||||
logging.info("restarting IBus")
|
||||
# we no longer try to restart since we sometimes ended up with more than one
|
||||
# ibus-daemon process (#6237). Instead we only exit ibus here, and start
|
||||
# ibus again below.
|
||||
bus.exit(False)
|
||||
bus.destroy()
|
||||
except Exception as e:
|
||||
logging.warning("Failed to restart IBus")
|
||||
logging.warning(e)
|
||||
logging.info('restarting IBus by subprocess')
|
||||
subprocess.run(['ibus', 'restart'], check=False)
|
||||
# give ibus a chance to shutdown (#6237)
|
||||
time.sleep(1) # 1s
|
||||
verify_ibus_daemon(True)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ from keyman_config import _, __version__, secure_lookup
|
|||
from keyman_config.canonical_language_code_utils import CanonicalLanguageCodeUtils
|
||||
from keyman_config.convertico import checkandsaveico, extractico
|
||||
from keyman_config.custom_keyboards import CustomKeyboards
|
||||
from keyman_config.dbus_util import get_keyman_config_service
|
||||
from keyman_config.dbus_util import keyboard_list_changed
|
||||
from keyman_config.fcitx_util import is_fcitx_running
|
||||
from keyman_config.get_kmp import (InstallLocation, get_keyboard_data,
|
||||
get_keyboard_dir, get_keyman_doc_dir,
|
||||
|
|
@ -351,5 +351,5 @@ def install_kmp(inputfile, sharedarea=False, language=None):
|
|||
else:
|
||||
return_value = InstallKmp().install_kmp_user(inputfile, language)
|
||||
|
||||
get_keyman_config_service().keyboard_list_changed()
|
||||
keyboard_list_changed()
|
||||
return return_value
|
||||
|
|
|
|||
|
|
@ -40,7 +40,8 @@ class SentryErrorHandling:
|
|||
def initialize_sentry(self):
|
||||
(enabled, reason) = self.is_sentry_enabled()
|
||||
if not enabled:
|
||||
print(reason, file=sys.stderr)
|
||||
if not self._is_running_tests():
|
||||
print(reason, file=sys.stderr)
|
||||
logging.info(reason)
|
||||
return (enabled, reason)
|
||||
else:
|
||||
|
|
@ -58,8 +59,8 @@ class SentryErrorHandling:
|
|||
return (True, '')
|
||||
|
||||
def is_sentry_enabled(self):
|
||||
if self._is_unit_test():
|
||||
return (False, 'Running unit tests, not reporting to Sentry')
|
||||
if self._is_running_tests():
|
||||
return (False, 'Running tests, not reporting to Sentry')
|
||||
elif self._get_environ_nosentry():
|
||||
return (False, 'Not reporting to Sentry because KEYMAN_NOSENTRY environment variable set')
|
||||
elif not __uploadsentry__:
|
||||
|
|
@ -94,12 +95,16 @@ class SentryErrorHandling:
|
|||
keyman_nosentry = os.environ.get('KEYMAN_NOSENTRY')
|
||||
return keyman_nosentry and (int(keyman_nosentry) == 1)
|
||||
|
||||
def _is_unit_test(self): # sourcery skip: use-any, use-next
|
||||
def _is_running_tests(self): # sourcery skip: use-any, use-next
|
||||
# The suggested refactorings (using any() or next()) don't work
|
||||
# when testing on Ubuntu 20.04
|
||||
for line in traceback.format_stack():
|
||||
if '/unittest/' in line:
|
||||
return True
|
||||
|
||||
if platform.node() == 'autopkgtest':
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _handle_enabled(self, enabled):
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from shutil import rmtree
|
|||
|
||||
from keyman_config import _
|
||||
from keyman_config.custom_keyboards import CustomKeyboards
|
||||
from keyman_config.dbus_util import get_keyman_config_service
|
||||
from keyman_config.dbus_util import keyboard_list_changed
|
||||
from keyman_config.fcitx_util import is_fcitx_running
|
||||
from keyman_config.get_kmp import (InstallLocation, get_keyboard_dir,
|
||||
get_keyman_doc_dir, get_keyman_font_dir)
|
||||
|
|
@ -152,5 +152,5 @@ def uninstall_kmp(packageID, sharedarea=False, removeLanguages=True):
|
|||
else:
|
||||
msg = _uninstall_kmp_common(InstallLocation.User, packageID, removeLanguages)
|
||||
|
||||
get_keyman_config_service().keyboard_list_changed()
|
||||
keyboard_list_changed()
|
||||
return msg
|
||||
|
|
|
|||
|
|
@ -109,6 +109,78 @@ def list_languages_for_keyboard(packageId, packageDir):
|
|||
print(result)
|
||||
|
||||
|
||||
def try_install_kmp(inputfile, arg, language=None, sharedarea=False):
|
||||
try:
|
||||
install_kmp(inputfile, sharedarea, language)
|
||||
except InstallError as e:
|
||||
if e.status == InstallStatus.Abort:
|
||||
logging.error("km-package-install: error: Failed to install %s", arg)
|
||||
logging.error(e.message)
|
||||
sys.exit(3)
|
||||
else:
|
||||
logging.warning(e.message)
|
||||
|
||||
|
||||
def install_package(args):
|
||||
if args.shared:
|
||||
if get_kmp_version_user(args.package):
|
||||
logging.warning(
|
||||
f'km-package-install: Warning: Keyboard {args.package} is also installed for the current user.'
|
||||
)
|
||||
installed_kmp_ver = get_kmp_version_shared(args.package)
|
||||
else:
|
||||
if get_kmp_version_shared(args.package):
|
||||
logging.warning(
|
||||
f'km-package-install: Warning: Keyboard {args.package} is already installed in the shared area.'
|
||||
)
|
||||
installed_kmp_ver = get_kmp_version_user(args.package)
|
||||
pkgdata = get_keyboard_data(args.package)
|
||||
if not pkgdata:
|
||||
logging.error("km-package-install: error: Could not download keyboard data for %s", args.package)
|
||||
sys.exit(3)
|
||||
pkg_version = secure_lookup(pkgdata, 'version')
|
||||
if installed_kmp_ver and pkg_version:
|
||||
if packaging.version.parse(installed_kmp_ver) > packaging.version.parse(pkg_version):
|
||||
if args.force:
|
||||
logging.warning(f'km-package-install: warning: The {args.package} keyboard ' +
|
||||
f'is already installed with a newer version {installed_kmp_ver}. ' +
|
||||
f'Downgrading to version {pkg_version}.')
|
||||
else:
|
||||
logging.error(f'km-package-install: error: The {args.package} keyboard is ' +
|
||||
f'already installed with a newer version {installed_kmp_ver}. ' +
|
||||
f'Not installing version {pkg_version}. Use `--force` to downgrade.')
|
||||
sys.exit(1)
|
||||
if args.force:
|
||||
uninstall_kmp(args.package, args.shared, False)
|
||||
|
||||
if kmpfile := get_kmp(args.package):
|
||||
try_install_kmp(kmpfile, f"keyboard package {args.package}", args.bcp47, args.shared)
|
||||
else:
|
||||
logging.error("km-package-install: error: Could not download keyboard package %s", args.package)
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
def install_file(parser, args):
|
||||
name, ext = os.path.splitext(args.file)
|
||||
if ext != ".kmp" or not is_zipfile(args.file):
|
||||
print_usage_and_exit(
|
||||
parser,
|
||||
f"km-package-install: Input file ${args.file} is not a kmp file."
|
||||
)
|
||||
if not os.path.isfile(args.file):
|
||||
print_usage_and_exit(
|
||||
parser,
|
||||
f"km-package-install: Keyman kmp file {args.file} not found."
|
||||
)
|
||||
try_install_kmp(args.file, f"file {args.file}", args.bcp47, args.shared)
|
||||
|
||||
|
||||
def print_usage_and_exit(parser, msg):
|
||||
parser.print_usage()
|
||||
logging.error(msg)
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Install a Keyman keyboard package, either a local .kmp file or specify a ' +
|
||||
|
|
@ -134,80 +206,24 @@ def main():
|
|||
sys.exit(4)
|
||||
|
||||
if args.package and args.file:
|
||||
parser.print_usage()
|
||||
logging.error(
|
||||
"km-package-install: error: too many arguments: either install a local kmp file or " +
|
||||
"specify a keyboard id to download and install.")
|
||||
sys.exit(2)
|
||||
|
||||
print_usage_and_exit(
|
||||
parser,
|
||||
"km-package-install: error: too many arguments: either install a local kmp file or " +
|
||||
"specify a keyboard id to download and install.",
|
||||
)
|
||||
if os.path.exists(os.path.join(keyman_cache_dir(), 'kmpdirlist')):
|
||||
os.remove(os.path.join(keyman_cache_dir(), 'kmpdirlist'))
|
||||
|
||||
def try_install_kmp(inputfile, arg, language=None, sharedarea=False):
|
||||
try:
|
||||
install_kmp(inputfile, sharedarea, language)
|
||||
except InstallError as e:
|
||||
if e.status == InstallStatus.Abort:
|
||||
logging.error("km-package-install: error: Failed to install %s", arg)
|
||||
logging.error(e.message)
|
||||
sys.exit(3)
|
||||
else:
|
||||
logging.warning(e.message)
|
||||
|
||||
if args.file:
|
||||
name, ext = os.path.splitext(args.file)
|
||||
if ext != ".kmp" or not is_zipfile(args.file):
|
||||
logging.error("km-package-install: Input file %s is not a kmp file.", args.file)
|
||||
logging.error("km-package-install -f <kmpfile>")
|
||||
sys.exit(2)
|
||||
|
||||
if not os.path.isfile(args.file):
|
||||
logging.error("km-package-install: Keyman kmp file %s not found.", args.file)
|
||||
logging.error("km-package-install -f <kmpfile>")
|
||||
sys.exit(2)
|
||||
try_install_kmp(args.file, f"file {args.file}", args.bcp47, args.shared)
|
||||
install_file(parser, args)
|
||||
elif args.package:
|
||||
if args.shared:
|
||||
if get_kmp_version_user(args.package):
|
||||
logging.warning(f'km-package-install: Warning: Keyboard {args.package} is ' +
|
||||
'also installed for the current user.')
|
||||
installed_kmp_ver = get_kmp_version_shared(args.package)
|
||||
else:
|
||||
if get_kmp_version_shared(args.package):
|
||||
logging.warning(f'km-package-install: Warning: Keyboard {args.package} is ' +
|
||||
'already installed in the shared area.')
|
||||
installed_kmp_ver = get_kmp_version_user(args.package)
|
||||
pkgdata = get_keyboard_data(args.package)
|
||||
if not pkgdata:
|
||||
logging.error("km-package-install: error: Could not download keyboard data for %s", args.package)
|
||||
sys.exit(3)
|
||||
pkg_version = secure_lookup(pkgdata, 'version')
|
||||
if installed_kmp_ver and pkg_version:
|
||||
if packaging.version.parse(installed_kmp_ver) > packaging.version.parse(pkg_version):
|
||||
if args.force:
|
||||
logging.warning(f'km-package-install: warning: The {args.package} keyboard ' +
|
||||
f'is already installed with a newer version {installed_kmp_ver}. ' +
|
||||
f'Downgrading to version {pkg_version}.')
|
||||
else:
|
||||
logging.error(f'km-package-install: error: The {args.package} keyboard is ' +
|
||||
f'already installed with a newer version {installed_kmp_ver}. ' +
|
||||
f'Not installing version {pkg_version}. Use `--force` to downgrade.')
|
||||
sys.exit(1)
|
||||
if args.force:
|
||||
uninstall_kmp(args.package, args.shared, False)
|
||||
|
||||
kmpfile = get_kmp(args.package)
|
||||
if kmpfile:
|
||||
try_install_kmp(kmpfile, f"keyboard package {args.package}", args.bcp47, args.shared)
|
||||
else:
|
||||
logging.error("km-package-install: error: Could not download keyboard package %s", args.package)
|
||||
sys.exit(2)
|
||||
install_package(args)
|
||||
else:
|
||||
parser.print_usage()
|
||||
logging.error(
|
||||
"km-package-install: error: no arguments: either install a local kmp file " +
|
||||
"or specify a keyboard package id to download and install.")
|
||||
sys.exit(2)
|
||||
print_usage_and_exit(
|
||||
parser,
|
||||
"km-package-install: error: no arguments: either install a local kmp file " +
|
||||
"or specify a keyboard package id to download and install.",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -325,9 +325,7 @@ class UninstallKmpTests(unittest.TestCase):
|
|||
self.mockUninstallDir = self._setupMock('keyman_config.uninstall_kmp._uninstall_dir')
|
||||
self.mockGetMetaData = self._setupMock('keyman_config.uninstall_kmp.get_metadata')
|
||||
self.mockCustomKeyboardsClass = self._setupMock('keyman_config.uninstall_kmp.CustomKeyboards')
|
||||
self.mockGetKeymanConfigService = self._setupMock('keyman_config.uninstall_kmp.get_keyman_config_service')
|
||||
self.mockKeymanConfigServiceManager = Mock()
|
||||
self.mockGetKeymanConfigService.return_value = self.mockKeymanConfigServiceManager
|
||||
self.mockKeyboardListChanged = self._setupMock('keyman_config.uninstall_kmp.keyboard_list_changed')
|
||||
|
||||
def _setupMock(self, arg0):
|
||||
patcher = patch(arg0)
|
||||
|
|
@ -341,9 +339,6 @@ class UninstallKmpTests(unittest.TestCase):
|
|||
keyboards = [{'id': 'foo1', 'languages': [{'id': 'en'}]}, {'id': 'bar2', 'languages': [{'id': 'fr'}]}]
|
||||
self.mockGetMetaData.return_value = (None, None, None, keyboards, None)
|
||||
|
||||
keyboardListChangedMock = Mock()
|
||||
self.mockKeymanConfigServiceManager.keyboard_list_changed = keyboardListChangedMock
|
||||
|
||||
# Execute
|
||||
uninstall_kmp('foo', False, True)
|
||||
|
||||
|
|
@ -351,5 +346,4 @@ class UninstallKmpTests(unittest.TestCase):
|
|||
self.mockUninstallKeyboardsFromIbus.assert_called_once_with(keyboards, '/tmp/foo')
|
||||
self.mockUninstallDir.assert_called()
|
||||
self.assertEqual(mockCustomKeyboardsInstance.remove.call_count, 2)
|
||||
self.assertEqual(keyboardListChangedMock.call_count, 1)
|
||||
|
||||
self.mockKeyboardListChanged.assert_called_once()
|
||||
|
|
|
|||
|
|
@ -365,7 +365,8 @@ NSString *const kKMLegacyApps = @"KMLegacyApps";
|
|||
[clientAppId isEqual: @"org.sil.app.builder.scripture.ScriptureAppBuilder"] ||
|
||||
[clientAppId isEqual: @"org.sil.app.builder.reading.ReadingAppBuilder"] ||
|
||||
[clientAppId isEqual: @"org.sil.app.builder.dictionary.DictionaryAppBuilder"] ||
|
||||
//[clientAppId isEqual: @"com.microsoft.Word"] || // 2020-11-24[mcd]: Appears to work well in Word 16.43, disable legacy by default
|
||||
[clientAppId isEqual: @"com.microsoft.Word"] ||
|
||||
// Microsoft Word removed on 2020-11-24 but added back on 2025-10-10 for issue #14160
|
||||
[clientAppId isEqual: @"org.openoffice.script"] ||
|
||||
// 2023-12-13[sgs]: Adobe apps automatically detected as non-compliant
|
||||
//[clientAppId isEqual: @"com.adobe.illustrator"] ||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ Read only
|
|||
|
||||
### Return Value
|
||||
|
||||
Presently set to [`"http://help.keymanweb.com/go"`]().
|
||||
Presently set to [`"https://help.keyman.com/go"`]().
|
||||
|
||||
## Description
|
||||
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ if(!keymanweb) {
|
|||
this.outerDiv = util.createElement('div'); // Container for UI (visible when KeymanWeb is active)
|
||||
this.innerDiv = util.createElement('div'); // inner div for UI
|
||||
this.kbdIcon = util.createElement('img');
|
||||
this.outerDiv.innerHTML = "<a href='http://keyman.com/web/' target='KeymanWebHelp'>"
|
||||
this.outerDiv.innerHTML = "<a href='https://keyman.com/developer/keymanweb/' target='KeymanWebHelp'>"
|
||||
+ "<img src='"+imgPath+"kmicon.gif' border='0' style='padding: 0px 2px 0 1px; margin:0px;' title='KeymanWeb' alt='KeymanWeb' /></a>"; /* I2081 */
|
||||
|
||||
let s=this.outerDiv.style;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
(*
|
||||
Name: UfrmOSKOnScreenKeyboard
|
||||
Copyright: Copyright (C) SIL International.
|
||||
Documentation:
|
||||
Description:
|
||||
Documentation:
|
||||
Description:
|
||||
Create Date: 14 Sep 2006
|
||||
|
||||
Modified Date: 8 Apr 2015
|
||||
Authors: mcdurdin
|
||||
Related Files:
|
||||
Dependencies:
|
||||
Related Files:
|
||||
Dependencies:
|
||||
|
||||
Bugs:
|
||||
Todo:
|
||||
Notes:
|
||||
Bugs:
|
||||
Todo:
|
||||
Notes:
|
||||
History: 14 Sep 2006 - mcdurdin - Initial version
|
||||
04 Dec 2006 - mcdurdin - Support exporting and printing
|
||||
04 Dec 2006 - mcdurdin - Fix shift states when pressing keys
|
||||
|
|
@ -72,6 +72,7 @@ type
|
|||
VKI: TVisualKeyboardInfo;
|
||||
VisualKeyboards: TVisualKeyboardInfoList;
|
||||
FCachedShiftState: TExtShiftState;
|
||||
IsSimulatedLControlDown: Boolean;
|
||||
function GetAsyncShiftState: TExtShiftState;
|
||||
procedure UpdateKeyboard(FLoading: Boolean);
|
||||
|
||||
|
|
@ -89,6 +90,7 @@ type
|
|||
procedure PrintKeyboard;
|
||||
|
||||
procedure ResetShiftStates; // I1144
|
||||
procedure OskModifierEvent(VKCode, Flags: DWORD);
|
||||
|
||||
function HasVisualKeyboard: Boolean; overload;
|
||||
function HasVisualKeyboard(KeymanID: Integer): Boolean; overload;
|
||||
|
|
@ -222,6 +224,58 @@ begin
|
|||
ShiftStateChange(fkcss, ass);
|
||||
end;
|
||||
|
||||
(**
|
||||
* Handles OSK modifier events received from
|
||||
* keyman32:PostVisualKeyboardModifierEvent in k32_visualkeyboardinterface.cpp.
|
||||
*
|
||||
* This takes the chiral VK_LCONTROL / VK_RCONTROL / VK_LMENU / VK_RMENU virtual
|
||||
* key codes.
|
||||
*
|
||||
* This currently deals only with one situation: when Windows posts a simulated
|
||||
* LControl key when AltGr is pressed on a European layout. See
|
||||
* serialkeyeventserver.cpp:WndProc for a deeper discussion of the key events
|
||||
* that are generated in this scenario.
|
||||
*
|
||||
* @param VKCode virtual key code of the modifier key, chiral for Alt and Ctrl
|
||||
* @param Flags as follows:
|
||||
* KEYEVENTF_EXTENDEDKEY 0x00000001 - extended bit is set on the scan code
|
||||
* KEYEVENTF_KEYUP 0x00000002 - key is being released
|
||||
* scan code mask 0x0FFF0000 - 12 bits for scan code
|
||||
* all other bits reserved
|
||||
*)
|
||||
procedure TfrmOSKOnScreenKeyboard.OskModifierEvent(VKCode, Flags: DWORD);
|
||||
var
|
||||
isUp: Boolean;
|
||||
scanCode: DWORD;
|
||||
const
|
||||
/// The normal scan code for left Control key
|
||||
SCAN_LEFT_CONTROL = $1D;
|
||||
/// The scan code generated by Windows when it simulates pressing left
|
||||
/// Control, when AltGr is pressed on a European layout; this is generated by
|
||||
/// Windows for compatibility reasons.
|
||||
SCAN_LEFT_CONTROL_SIMULATED = $21D;
|
||||
begin
|
||||
// not used: extended := (Flags and KEYEVENTF_EXTENDEDKEY) <> 0;
|
||||
isUp := (Flags and KEYEVENTF_KEYUP) <> 0;
|
||||
|
||||
// In order to identify the Windows simulated left control scan code, we need
|
||||
// to match on more bits than the normal 8 bit scan code.
|
||||
scanCode := (Flags and $0FFF0000) shr 16;
|
||||
|
||||
// If the Left Control key is simulated by Windows, then we want to ignore it
|
||||
// because in reality, AltGr has been pressed on a European keyboard. However,
|
||||
// if the Left Control key is being released, then we should reset the flag
|
||||
// whether it comes from the Windows simulation or from another source (real
|
||||
// or injected), so that we don't end up with a stuck left control key
|
||||
if (scanCode = SCAN_LEFT_CONTROL_SIMULATED) and not isUp then
|
||||
IsSimulatedLControlDown := True
|
||||
else if ((scanCode = SCAN_LEFT_CONTROL) or (scanCode = SCAN_LEFT_CONTROL_SIMULATED)) and isUp then
|
||||
IsSimulatedLControlDown := False;
|
||||
|
||||
// TODO: in the future, we might be able to eliminate tmrCheck and make all
|
||||
// modifier updates go through the WH_KEYBOARD_LL hook.
|
||||
end;
|
||||
|
||||
procedure TfrmOSKOnScreenKeyboard.ShiftStateChange(kbdShift, asyncShift: TExtShiftState);
|
||||
procedure PrepState(fkcss, ass: TExtShiftState; shift: TExtShiftStateValue; vk: Integer);
|
||||
var
|
||||
|
|
@ -317,10 +371,10 @@ begin
|
|||
FNewShiftState := FNewShiftState - [essLCtrl, essRCtrl];
|
||||
FChanged := True;
|
||||
end;
|
||||
|
||||
|
||||
if FChanged then
|
||||
ShiftStateChange(FNewShiftState, FShiftState);
|
||||
|
||||
|
||||
FShiftState := FNewShiftState;
|
||||
kbd.ShiftState := FShiftState;
|
||||
UpdateKeyboard(False);
|
||||
|
|
@ -357,36 +411,28 @@ begin
|
|||
end;
|
||||
|
||||
function TfrmOSKOnScreenKeyboard.GetAsyncShiftState: TExtShiftState;
|
||||
var
|
||||
lctrl: SHORT;
|
||||
ralt: SHORT;
|
||||
begin
|
||||
Result := [];
|
||||
if (GetAsyncKeyState(VK_SHIFT) and $8000) = $8000 then Result := Result + [essShift];
|
||||
if kbd.LRShift then
|
||||
begin
|
||||
if (GetAsyncKeyState(VK_LCONTROL) and $8000) = $8000 then Result := Result + [essLCtrl];
|
||||
lctrl := GetAsyncKeyState(VK_LCONTROL);
|
||||
ralt := GetAsyncKeyState(VK_RMENU);
|
||||
|
||||
if not IsSimulatedLControlDown and ((lctrl and $8000) = $8000) then
|
||||
Result := Result + [essLCtrl];
|
||||
if (GetAsyncKeyState(VK_RCONTROL) and $8000) = $8000 then Result := Result + [essRCtrl];
|
||||
if (GetAsyncKeyState(VK_LMENU) and $8000) = $8000 then Result := Result + [essLAlt];
|
||||
if (GetAsyncKeyState(VK_RMENU) and $8000) = $8000 then Result := Result + [essRAlt];
|
||||
if (ralt and $8000) = $8000 then Result := Result + [essRAlt];
|
||||
end
|
||||
else
|
||||
begin
|
||||
if (GetAsyncKeyState(VK_CONTROL) and $8000) = $8000 then Result := Result + [essCtrl];
|
||||
if (GetAsyncKeyState(VK_MENU) and $8000) = $8000 then Result := Result + [essAlt];
|
||||
end;
|
||||
|
||||
(*Result := [];
|
||||
if (GetAsyncKeyState(VK_SHIFT) and $8000) = $8000 then Result := Result + [essShift];
|
||||
if kbd.LRShift then
|
||||
begin
|
||||
if (GetAsyncKeyState(VK_LCONTROL) and $8000) = $8000 then Result := Result + [essLCtrl];
|
||||
if (GetAsyncKeyState(VK_RCONTROL) and $8000) = $8000 then Result := Result + [essRCtrl];
|
||||
if (GetAsyncKeyState(VK_LMENU) and $8000) = $8000 then Result := Result + [essLAlt];
|
||||
if (GetAsyncKeyState(VK_RMENU) and $8000) = $8000 then Result := Result + [essRAlt];
|
||||
end
|
||||
else
|
||||
begin
|
||||
if (GetAsyncKeyState(VK_CONTROL) and $8000) = $8000 then Result := Result + [essCtrl];
|
||||
if (GetAsyncKeyState(VK_MENU) and $8000) = $8000 then Result := Result + [essAlt];
|
||||
end;*)
|
||||
end;
|
||||
|
||||
function TfrmOSKOnScreenKeyboard.HasVisualKeyboard(KeymanID: Integer): Boolean;
|
||||
|
|
@ -474,7 +520,7 @@ begin
|
|||
ss := ExtShiftStateToVkShiftState(kbd.ShiftState);
|
||||
ssi := ValidExtShiftStateIndex(kbd.ShiftState);
|
||||
end;
|
||||
|
||||
|
||||
for i := 0 to VKI.Keyboard.Keys.Count - 1 do
|
||||
begin
|
||||
if not FPositional then
|
||||
|
|
|
|||
|
|
@ -203,6 +203,7 @@ type
|
|||
procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST;
|
||||
|
||||
procedure WMUserFormShown(var Message: TMessage); message WM_USER_FormShown; // I4225
|
||||
procedure WMKeymanOskModifierEvent(var Message: TMessage); message WM_KEYMAN_OSK_MODIFIER_EVENT;
|
||||
|
||||
procedure UpdatePanels;
|
||||
procedure SetActivePage(Page: TOSKActivePage);
|
||||
|
|
@ -998,6 +999,12 @@ begin
|
|||
inherited;
|
||||
end;
|
||||
|
||||
procedure TfrmVisualKeyboard.WMKeymanOskModifierEvent(var Message: TMessage);
|
||||
begin
|
||||
if Assigned(FOnScreenKeyboard) then
|
||||
FOnScreenKeyboard.OskModifierEvent(Message.WParam, Message.LParam);
|
||||
end;
|
||||
|
||||
procedure TfrmVisualKeyboard.AdjustToClosestMonitor(var x, y, cx, cy: Integer);
|
||||
var
|
||||
m: TMonitor;
|
||||
|
|
|
|||
|
|
@ -176,10 +176,15 @@ LRESULT _kmnLowLevelKeyboardProc(
|
|||
if (GetKeyState(VK_LSHIFT) < 0) {
|
||||
FHotkeyShiftState |= HK_SHIFT;
|
||||
}
|
||||
|
||||
if (GetKeyState(VK_RSHIFT) < 0) {
|
||||
FHotkeyShiftState |= AllowRightModifierHotkey ? HK_SHIFT : HK_RSHIFT_INVALID;
|
||||
}
|
||||
|
||||
if(isModifierKey(hs->vkCode)) {
|
||||
PostVisualKeyboardModifierEvent(hs->vkCode, LLKHFFlagstoWMKeymanKeyEventFlags(hs));
|
||||
}
|
||||
|
||||
//TODO: #8064. Can remove debug message once issue #8064 is resolved
|
||||
SendDebugMessageFormat("[FHotkeyShiftState:%x]", FHotkeyShiftState);
|
||||
|
||||
|
|
|
|||
65
windows/src/engine/keyman32/k32_visualkeyboardinterface.cpp
Normal file
65
windows/src/engine/keyman32/k32_visualkeyboardinterface.cpp
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*
|
||||
* Created by mcdurdin on 2025-10-08
|
||||
*
|
||||
* Interface between keyman32.dll and the visual keyboard module hosted in
|
||||
* keyman.exe
|
||||
*/
|
||||
#include "pch.h"
|
||||
|
||||
// These messages should correspond to the UfrmOSKOnScreenKeyboard section in
|
||||
// UserMessages.pas. Only messages used in keyman32 are currently defined here.
|
||||
#define WM_KEYMAN_OSK_MODIFIER_EVENT (WM_USER + 141)
|
||||
|
||||
/**
|
||||
* Tell the visual keyboard module that modifier keys on the physical keyboard
|
||||
* have been pressed or released, in order to update the display of the visual
|
||||
* keyboard. This is sent from the low level keyboard hook, so is an in-process
|
||||
* single thread event.
|
||||
*
|
||||
* This takes the chiral VK_LCONTROL / VK_RCONTROL / VK_LMENU / VK_RMENU virtual
|
||||
* key codes.
|
||||
*
|
||||
* This function exists primarily to support workaround of the Windows hack of
|
||||
* generating LCtrl+RAlt when AltGr is pressed on European layouts. The visual
|
||||
* keyboard module includes special handling for the custom scan code of 0x21D
|
||||
* for the fudged LCtrl key.
|
||||
*
|
||||
* @param vkCode virtual key code of the modifier key, chiral for Alt and Ctrl
|
||||
* @param flags as follows:
|
||||
* KEYEVENTF_EXTENDEDKEY 0x00000001 - extended bit is set on the scan code
|
||||
* KEYEVENTF_KEYUP 0x00000002 - key is being released
|
||||
* scan code mask 0x0FFF0000 - 12 bits for scan code
|
||||
* all other bits reserved
|
||||
*/
|
||||
void PostVisualKeyboardModifierEvent(UINT vkCode, DWORD flags) {
|
||||
PostMessageToVisualKeyboardWindowIfExists(WM_KEYMAN_OSK_MODIFIER_EVENT, vkCode, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the handle to the Visual Keyboard window in keyman.exe, if it is open,
|
||||
* and returns NULL if not found.
|
||||
*
|
||||
* @return HWND Handle to the Visual Keyboard window
|
||||
*/
|
||||
HWND GetVisualKeyboardWindow() {
|
||||
return FindWindow("TfrmVisualKeyboard", NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post a message to the Visual Keyboard window in keyman.exe, if it is open.
|
||||
*
|
||||
* @param msg Windows message
|
||||
* @param wParam WPARAM
|
||||
* @param lParam LPARAM
|
||||
* @return BOOL FALSE if window does exist or on PostMessage failure
|
||||
*/
|
||||
BOOL PostMessageToVisualKeyboardWindowIfExists(UINT msg, WPARAM wParam, LPARAM lParam) {
|
||||
HWND hwnd = GetVisualKeyboardWindow();
|
||||
if(hwnd == NULL) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return PostMessage(hwnd, msg, wParam, lParam);
|
||||
}
|
||||
14
windows/src/engine/keyman32/k32_visualkeyboardinterface.h
Normal file
14
windows/src/engine/keyman32/k32_visualkeyboardinterface.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*
|
||||
* Created by mcdurdin on 2025-10-08
|
||||
*
|
||||
* Interface between keyman32.dll and the visual keyboard module hosted in
|
||||
* keyman.exe
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
HWND GetVisualKeyboardWindow();
|
||||
void PostVisualKeyboardModifierEvent(UINT vkCode, DWORD flags);
|
||||
BOOL PostMessageToVisualKeyboardWindowIfExists(UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
|
|
@ -349,6 +349,7 @@
|
|||
<ClCompile Include="K32_load.cpp" />
|
||||
<ClCompile Include="k32_lowlevelkeyboardhook.cpp" />
|
||||
<ClCompile Include="k32_tsf.cpp" />
|
||||
<ClCompile Include="k32_visualkeyboardinterface.cpp" />
|
||||
<ClCompile Include="keybd_shift.cpp" />
|
||||
<ClCompile Include="keyboardoptions.cpp" />
|
||||
<ClCompile Include="keyman32.cpp" />
|
||||
|
|
@ -441,6 +442,7 @@
|
|||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="hotkeys.h" />
|
||||
<ClInclude Include="k32_tsf.h" />
|
||||
<ClInclude Include="k32_visualkeyboardinterface.h" />
|
||||
<ClInclude Include="KBD.H" />
|
||||
<ClInclude Include="keyboardoptions.h" />
|
||||
<ClInclude Include="keyman64.h" />
|
||||
|
|
|
|||
|
|
@ -262,6 +262,7 @@ void keybd_shift(LPINPUT pInputs, int* n, BOOL isReset, LPBYTE const kbd);
|
|||
#include "..\..\..\..\common\windows\cpp\include\crc32.h"
|
||||
|
||||
#include "k32_tsf.h"
|
||||
#include "k32_visualkeyboardinterface.h"
|
||||
|
||||
void ReportActiveKeyboard(WORD wCommand); // I3933 // I3949
|
||||
void SelectKeyboardHKL(DWORD hkl, BOOL foreground); // I3933 // I3949 // I4271
|
||||
|
|
|
|||
|
|
@ -170,10 +170,11 @@ LRESULT _kmnGetMessageProc(int nCode, WPARAM wParam, LPARAM lParam)
|
|||
}
|
||||
|
||||
// I1337 - move mousewheel message checking before the PM_REMOVE handling - otherwise we can't pre-modify the message
|
||||
// TODO: Move to WH_MOUSE_LL proc in main process
|
||||
// TODO: Move to WH_MOUSE_LL proc in main process?
|
||||
if(mp->message == WM_MOUSEWHEEL)
|
||||
{
|
||||
HWND hwndMouseWheel = FindWindow("TfrmVisualKeyboard", NULL);
|
||||
// TODO: move most of this logic to k32_visualkeyboardinterface.cpp
|
||||
HWND hwndMouseWheel = GetVisualKeyboardWindow();
|
||||
if(hwndMouseWheel && GetWindowThreadProcessId(mp->hwnd, NULL) != GetWindowThreadProcessId(hwndMouseWheel, NULL))
|
||||
{
|
||||
RECT rcMouseWheel;
|
||||
|
|
|
|||
|
|
@ -475,6 +475,10 @@ private:
|
|||
|
||||
There is a possibility that some apps may try and sniff that 0x21D scan code and get confused because Keyman
|
||||
doesn't emit it. Hopefully this is rare.
|
||||
|
||||
See also:
|
||||
* PostVisualKeyboardModifierEvent in k32_visualkeyboardinterface.cpp
|
||||
* TfrmOSKOnScreenKeyboard.OskModifierEvent in UfrmOSKOnScreenKeyboard.cpp
|
||||
*/
|
||||
INPUT input[2];
|
||||
input[0].type = INPUT_KEYBOARD;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
# keyboard_ll_identifier
|
||||
|
||||
This test app allows us to view details of key events as they are received by
|
||||
the low level keyboard hook. There are subtle differences between these key
|
||||
events and those received in WM_KEY* messages, e.g. see discussions on
|
||||
simulated left control in k32_visualkeyboardinterface.cpp.
|
||||
|
||||
The app currently only reports on modifier keys, but it's easy to tweak for
|
||||
other reporting as needed.
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
program keyboard_ll_identifier;
|
||||
|
||||
uses
|
||||
Vcl.Forms,
|
||||
keyboard_ll_identifier_unit in 'keyboard_ll_identifier_unit.pas' {Form1};
|
||||
|
||||
{$R *.res}
|
||||
|
||||
begin
|
||||
Application.Initialize;
|
||||
Application.MainFormOnTaskbar := True;
|
||||
Application.CreateForm(TForm1, Form1);
|
||||
Application.Run;
|
||||
end.
|
||||
|
|
@ -0,0 +1,970 @@
|
|||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{C945C654-1D70-4248-8513-09FBD3715ACE}</ProjectGuid>
|
||||
<ProjectVersion>18.8</ProjectVersion>
|
||||
<FrameworkType>VCL</FrameworkType>
|
||||
<MainSource>keyboard_ll_identifier.dpr</MainSource>
|
||||
<Base>True</Base>
|
||||
<Config Condition="'$(Config)'==''">Debug</Config>
|
||||
<Platform Condition="'$(Platform)'==''">Win32</Platform>
|
||||
<TargetedPlatforms>1</TargetedPlatforms>
|
||||
<AppType>Application</AppType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''">
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Base)'=='true') or '$(Base_Win32)'!=''">
|
||||
<Base_Win32>true</Base_Win32>
|
||||
<CfgParent>Base</CfgParent>
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Base)'=='true') or '$(Base_Win64)'!=''">
|
||||
<Base_Win64>true</Base_Win64>
|
||||
<CfgParent>Base</CfgParent>
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Config)'=='Debug' or '$(Cfg_1)'!=''">
|
||||
<Cfg_1>true</Cfg_1>
|
||||
<CfgParent>Base</CfgParent>
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_1)'=='true') or '$(Cfg_1_Win32)'!=''">
|
||||
<Cfg_1_Win32>true</Cfg_1_Win32>
|
||||
<CfgParent>Cfg_1</CfgParent>
|
||||
<Cfg_1>true</Cfg_1>
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Config)'=='Release' or '$(Cfg_2)'!=''">
|
||||
<Cfg_2>true</Cfg_2>
|
||||
<CfgParent>Base</CfgParent>
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_2)'=='true') or '$(Cfg_2_Win32)'!=''">
|
||||
<Cfg_2_Win32>true</Cfg_2_Win32>
|
||||
<CfgParent>Cfg_2</CfgParent>
|
||||
<Cfg_2>true</Cfg_2>
|
||||
<Base>true</Base>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Base)'!=''">
|
||||
<DCC_DcuOutput>.\$(Platform)\$(Config)</DCC_DcuOutput>
|
||||
<DCC_ExeOutput>.\$(Platform)\$(Config)</DCC_ExeOutput>
|
||||
<DCC_E>false</DCC_E>
|
||||
<DCC_N>false</DCC_N>
|
||||
<DCC_S>false</DCC_S>
|
||||
<DCC_F>false</DCC_F>
|
||||
<DCC_K>false</DCC_K>
|
||||
<DCC_Namespace>System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace)</DCC_Namespace>
|
||||
<Icon_MainIcon>$(BDS)\bin\delphi_PROJECTICON.ico</Icon_MainIcon>
|
||||
<UWP_DelphiLogo44>$(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png</UWP_DelphiLogo44>
|
||||
<UWP_DelphiLogo150>$(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png</UWP_DelphiLogo150>
|
||||
<SanitizedProjectName>keyboard_ll_identifier</SanitizedProjectName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Base_Win32)'!=''">
|
||||
<DCC_UsePackage>DBXSqliteDriver;bindcompdbx;IndyIPCommon;RESTComponents;DBXInterBaseDriver;vcl;IndyIPServer;vclactnband;vclFireDAC;IndySystem;tethering;svnui;mbColorLibD10;dsnapcon;FireDACADSDriver;scFontCombo;FireDACMSAccDriver;fmxFireDAC;vclimg;TeeDB;FireDAC;Jcl;vcltouch;JvCore;vcldb;bindcompfmx;svn;FireDACSqliteDriver;FireDACPgDriver;inetdb;CEF4Delphi;FMXTee;soaprtl;DbxCommonDriver;FmxTeeUI;FireDACIBDriver;fmx;fmxdae;xmlrtl;soapmidas;Tee;fmxobj;vclwinx;common_components;rtl;DbxClientDriver;CustomIPTransport;vcldsnap;dbexpress;IndyCore;vclx;browse4folder;bindcomp;appanalytics;dsnap;FireDACCommon;IndyIPClient;bindcompvcl;RESTBackendComponents;TeeUI;VCLRESTComponents;soapserver;dbxcds;VclSmp;JvDocking;adortl;developer_components;JclVcl;vclie;bindengine;DBXMySQLDriver;CloudService;dsnapxml;FireDACMySQLDriver;dbrtl;inetdbxpress;IndyProtocols;keyman_components;FireDACCommonODBC;FireDACCommonDriver;inet;fmxase;$(DCC_UsePackage)</DCC_UsePackage>
|
||||
<DCC_Namespace>Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)</DCC_Namespace>
|
||||
<BT_BuildType>Debug</BT_BuildType>
|
||||
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
|
||||
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
|
||||
<VerInfo_Locale>1033</VerInfo_Locale>
|
||||
<Manifest_File>$(BDS)\bin\default_app.manifest</Manifest_File>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Base_Win64)'!=''">
|
||||
<DCC_UsePackage>DBXSqliteDriver;bindcompdbx;IndyIPCommon;RESTComponents;DBXInterBaseDriver;vcl;IndyIPServer;vclactnband;vclFireDAC;IndySystem;tethering;dsnapcon;FireDACADSDriver;FireDACMSAccDriver;fmxFireDAC;vclimg;TeeDB;FireDAC;Jcl;vcltouch;vcldb;bindcompfmx;FireDACSqliteDriver;FireDACPgDriver;inetdb;CEF4Delphi;FMXTee;soaprtl;DbxCommonDriver;FmxTeeUI;FireDACIBDriver;fmx;fmxdae;xmlrtl;soapmidas;Tee;fmxobj;vclwinx;rtl;DbxClientDriver;CustomIPTransport;vcldsnap;dbexpress;IndyCore;vclx;bindcomp;appanalytics;dsnap;FireDACCommon;IndyIPClient;bindcompvcl;RESTBackendComponents;TeeUI;VCLRESTComponents;soapserver;dbxcds;VclSmp;adortl;JclVcl;vclie;bindengine;DBXMySQLDriver;CloudService;dsnapxml;FireDACMySQLDriver;dbrtl;inetdbxpress;IndyProtocols;FireDACCommonODBC;FireDACCommonDriver;inet;fmxase;$(DCC_UsePackage)</DCC_UsePackage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Cfg_1)'!=''">
|
||||
<DCC_Define>DEBUG;$(DCC_Define)</DCC_Define>
|
||||
<DCC_DebugDCUs>true</DCC_DebugDCUs>
|
||||
<DCC_Optimize>false</DCC_Optimize>
|
||||
<DCC_GenerateStackFrames>true</DCC_GenerateStackFrames>
|
||||
<DCC_DebugInfoInExe>true</DCC_DebugInfoInExe>
|
||||
<DCC_RemoteDebug>true</DCC_RemoteDebug>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Cfg_1_Win32)'!=''">
|
||||
<DCC_RemoteDebug>false</DCC_RemoteDebug>
|
||||
<AppEnableRuntimeThemes>true</AppEnableRuntimeThemes>
|
||||
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Cfg_2)'!=''">
|
||||
<DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
|
||||
<DCC_Define>RELEASE;$(DCC_Define)</DCC_Define>
|
||||
<DCC_SymbolReferenceInfo>0</DCC_SymbolReferenceInfo>
|
||||
<DCC_DebugInformation>0</DCC_DebugInformation>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Cfg_2_Win32)'!=''">
|
||||
<AppEnableRuntimeThemes>true</AppEnableRuntimeThemes>
|
||||
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<DelphiCompile Include="$(MainSource)">
|
||||
<MainSource>MainSource</MainSource>
|
||||
</DelphiCompile>
|
||||
<DCCReference Include="keyboard_ll_identifier_unit.pas">
|
||||
<Form>Form1</Form>
|
||||
<FormType>dfm</FormType>
|
||||
</DCCReference>
|
||||
<BuildConfiguration Include="Release">
|
||||
<Key>Cfg_2</Key>
|
||||
<CfgParent>Base</CfgParent>
|
||||
</BuildConfiguration>
|
||||
<BuildConfiguration Include="Base">
|
||||
<Key>Base</Key>
|
||||
</BuildConfiguration>
|
||||
<BuildConfiguration Include="Debug">
|
||||
<Key>Cfg_1</Key>
|
||||
<CfgParent>Base</CfgParent>
|
||||
</BuildConfiguration>
|
||||
</ItemGroup>
|
||||
<ProjectExtensions>
|
||||
<Borland.Personality>Delphi.Personality.12</Borland.Personality>
|
||||
<Borland.ProjectType>Application</Borland.ProjectType>
|
||||
<BorlandProject>
|
||||
<Delphi.Personality>
|
||||
<Source>
|
||||
<Source Name="MainSource">keyboard_ll_identifier.dpr</Source>
|
||||
</Source>
|
||||
</Delphi.Personality>
|
||||
<Deployment Version="3">
|
||||
<DeployFile LocalName="Win32\Debug\keyboard_ll_identifier.exe" Configuration="Debug" Class="ProjectOutput">
|
||||
<Platform Name="Win32">
|
||||
<RemoteName>keyboard_ll_identifier.exe</RemoteName>
|
||||
<Overwrite>true</Overwrite>
|
||||
</Platform>
|
||||
</DeployFile>
|
||||
<DeployClass Name="AdditionalDebugSymbols">
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="OSX32">
|
||||
<RemoteDir>Contents\MacOS</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Win32">
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="AndroidClassesDexFile">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>classes</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>classes</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="AndroidFileProvider">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\xml</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\xml</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="AndroidGDBServer">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="AndroidLibnativeArmeabiFile">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>library\lib\armeabi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>library\lib\armeabi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="AndroidLibnativeArmeabiv7aFile">
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="AndroidLibnativeMipsFile">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>library\lib\mips</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>library\lib\mips</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="AndroidServiceOutput">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>library\lib\arm64-v8a</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="AndroidServiceOutput_Android32">
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="AndroidSplashImageDef">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="AndroidSplashStyles">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\values</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\values</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="AndroidSplashStylesV21">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\values-v21</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\values-v21</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_Colors">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\values</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\values</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_DefaultAppIcon">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_LauncherIcon144">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_LauncherIcon36">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-ldpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-ldpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_LauncherIcon48">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-mdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-mdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_LauncherIcon72">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-hdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-hdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_LauncherIcon96">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-xhdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-xhdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_NotificationIcon24">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-mdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-mdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_NotificationIcon36">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-hdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-hdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_NotificationIcon48">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-xhdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-xhdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_NotificationIcon72">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_NotificationIcon96">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_SplashImage426">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-small</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-small</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_SplashImage470">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-normal</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-normal</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_SplashImage640">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-large</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-large</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_SplashImage960">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\drawable-xlarge</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\drawable-xlarge</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="Android_Strings">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>res\values</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>res\values</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="DebugSymbols">
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="OSX32">
|
||||
<RemoteDir>Contents\MacOS</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Win32">
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="DependencyFramework">
|
||||
<Platform Name="OSX32">
|
||||
<RemoteDir>Contents\MacOS</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.framework</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="OSX64">
|
||||
<RemoteDir>Contents\MacOS</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.framework</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="Win32">
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="DependencyModule">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.dylib</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.dylib</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.dylib</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="OSX32">
|
||||
<RemoteDir>Contents\MacOS</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.dylib</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="OSX64">
|
||||
<RemoteDir>Contents\MacOS</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.dylib</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="Win32">
|
||||
<Operation>0</Operation>
|
||||
<Extensions>.dll;.bpl</Extensions>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Required="true" Name="DependencyPackage">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.dylib</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.dylib</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.dylib</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="OSX32">
|
||||
<RemoteDir>Contents\MacOS</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.dylib</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="OSX64">
|
||||
<RemoteDir>Contents\MacOS</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
<Extensions>.dylib</Extensions>
|
||||
</Platform>
|
||||
<Platform Name="Win32">
|
||||
<Operation>0</Operation>
|
||||
<Extensions>.bpl</Extensions>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="File">
|
||||
<Platform Name="Android">
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
<Platform Name="OSX32">
|
||||
<RemoteDir>Contents\Resources\StartUp\</RemoteDir>
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
<Platform Name="OSX64">
|
||||
<RemoteDir>Contents\Resources\StartUp\</RemoteDir>
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Win32">
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPad_Launch1024x768">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPad_Launch1536x2048">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPad_Launch1668">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPad_Launch1668x2388">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPad_Launch2048x1536">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPad_Launch2048x2732">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPad_Launch2224">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPad_Launch2388x1668">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPad_Launch2732x2048">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPad_Launch768x1024">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_Launch1125">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_Launch1136x640">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_Launch1242">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_Launch1242x2688">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_Launch1334">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_Launch1792">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_Launch2208">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_Launch2436">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_Launch2688x1242">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_Launch320">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_Launch640">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_Launch640x1136">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_Launch750">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="iPhone_Launch828">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectAndroidManifest">
|
||||
<Platform Name="Android">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectiOSDeviceDebug">
|
||||
<Platform Name="iOSDevice32">
|
||||
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectiOSDeviceResourceRules">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectiOSEntitlements">
|
||||
<Platform Name="iOSDevice32">
|
||||
<RemoteDir>..\</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<RemoteDir>..\</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectiOSInfoPList">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectiOSResource">
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectOSXDebug">
|
||||
<Platform Name="OSX64">
|
||||
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectOSXEntitlements">
|
||||
<Platform Name="OSX32">
|
||||
<RemoteDir>..\</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="OSX64">
|
||||
<RemoteDir>..\</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectOSXInfoPList">
|
||||
<Platform Name="OSX32">
|
||||
<RemoteDir>Contents</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="OSX64">
|
||||
<RemoteDir>Contents</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectOSXResource">
|
||||
<Platform Name="OSX32">
|
||||
<RemoteDir>Contents\Resources</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="OSX64">
|
||||
<RemoteDir>Contents\Resources</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Required="true" Name="ProjectOutput">
|
||||
<Platform Name="Android">
|
||||
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>library\lib\arm64-v8a</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSDevice64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="iOSSimulator">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Linux64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="OSX32">
|
||||
<RemoteDir>Contents\MacOS</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="OSX64">
|
||||
<RemoteDir>Contents\MacOS</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Win32">
|
||||
<Operation>0</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectOutput_Android32">
|
||||
<Platform Name="Android64">
|
||||
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="ProjectUWPManifest">
|
||||
<Platform Name="Win32">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Win64">
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="UWP_DelphiLogo150">
|
||||
<Platform Name="Win32">
|
||||
<RemoteDir>Assets</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Win64">
|
||||
<RemoteDir>Assets</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<DeployClass Name="UWP_DelphiLogo44">
|
||||
<Platform Name="Win32">
|
||||
<RemoteDir>Assets</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
<Platform Name="Win64">
|
||||
<RemoteDir>Assets</RemoteDir>
|
||||
<Operation>1</Operation>
|
||||
</Platform>
|
||||
</DeployClass>
|
||||
<ProjectRoot Platform="iOSDevice64" Name="$(PROJECTNAME).app"/>
|
||||
<ProjectRoot Platform="Win64" Name="$(PROJECTNAME)"/>
|
||||
<ProjectRoot Platform="iOSDevice32" Name="$(PROJECTNAME).app"/>
|
||||
<ProjectRoot Platform="Linux64" Name="$(PROJECTNAME)"/>
|
||||
<ProjectRoot Platform="Win32" Name="$(PROJECTNAME)"/>
|
||||
<ProjectRoot Platform="OSX32" Name="$(PROJECTNAME).app"/>
|
||||
<ProjectRoot Platform="Android" Name="$(PROJECTNAME)"/>
|
||||
<ProjectRoot Platform="OSX64" Name="$(PROJECTNAME).app"/>
|
||||
<ProjectRoot Platform="iOSSimulator" Name="$(PROJECTNAME).app"/>
|
||||
<ProjectRoot Platform="Android64" Name="$(PROJECTNAME)"/>
|
||||
</Deployment>
|
||||
<Platforms>
|
||||
<Platform value="Win32">True</Platform>
|
||||
<Platform value="Win64">False</Platform>
|
||||
</Platforms>
|
||||
</BorlandProject>
|
||||
<ProjectFileVersion>12</ProjectFileVersion>
|
||||
</ProjectExtensions>
|
||||
<Import Project="$(BDS)\Bin\CodeGear.Delphi.Targets" Condition="Exists('$(BDS)\Bin\CodeGear.Delphi.Targets')"/>
|
||||
<Import Project="$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj" Condition="Exists('$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj')"/>
|
||||
<Import Project="$(MSBuildProjectName).deployproj" Condition="Exists('$(MSBuildProjectName).deployproj')"/>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
object Form1: TForm1
|
||||
Left = 0
|
||||
Top = 0
|
||||
Caption = 'Form1'
|
||||
ClientHeight = 347
|
||||
ClientWidth = 728
|
||||
Color = clBtnFace
|
||||
Font.Charset = DEFAULT_CHARSET
|
||||
Font.Color = clWindowText
|
||||
Font.Height = -11
|
||||
Font.Name = 'Tahoma'
|
||||
Font.Style = []
|
||||
OldCreateOrder = False
|
||||
OnCreate = FormCreate
|
||||
OnDestroy = FormDestroy
|
||||
PixelsPerInch = 96
|
||||
TextHeight = 13
|
||||
object Memo1: TMemo
|
||||
Left = 8
|
||||
Top = 8
|
||||
Width = 697
|
||||
Height = 321
|
||||
Lines.Strings = (
|
||||
'Memo1')
|
||||
TabOrder = 0
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
unit keyboard_ll_identifier_unit;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
|
||||
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
|
||||
|
||||
type
|
||||
TForm1 = class(TForm)
|
||||
Memo1: TMemo;
|
||||
procedure FormCreate(Sender: TObject);
|
||||
procedure FormDestroy(Sender: TObject);
|
||||
private
|
||||
procedure AppOnMessage(var Msg: TMsg; var Handled: Boolean);
|
||||
{ Private declarations }
|
||||
public
|
||||
{ Public declarations }
|
||||
end;
|
||||
|
||||
var
|
||||
Form1: TForm1;
|
||||
|
||||
implementation
|
||||
|
||||
{$R *.dfm}
|
||||
|
||||
var
|
||||
hh: Cardinal;
|
||||
|
||||
type
|
||||
TKbdLlHookStruct = record
|
||||
vkCode, scanCode, flags, time: DWORD;
|
||||
dwExtraInfo: ULONG_PTR;
|
||||
end;
|
||||
PKbdLlHookStruct = ^TKbdLlHookStruct;
|
||||
|
||||
function kbdll(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
|
||||
var
|
||||
s: PKbdllhookstruct;
|
||||
begin
|
||||
if nCode < 0 then
|
||||
begin
|
||||
Exit(CallNextHookEx(hh, nCode, wParam, lParam));
|
||||
end;
|
||||
|
||||
s := PkbdllHookStruct(lParam);
|
||||
|
||||
if s.vkCode in [VK_LCONTROL, VK_RCONTROL, VK_LMENU, VK_RMENU, VK_LSHIFT, VK_RSHIFT, VK_CONTROL, VK_SHIFT, VK_MENU] then
|
||||
if Assigned(Form1) then
|
||||
begin
|
||||
Form1.memo1.Lines.Add(Format('%08.8x %08.8x %08.8x', [s.vkCode, s.scanCode, s.flags]));
|
||||
end;
|
||||
|
||||
Exit(CallNextHookEx(hh, nCode, wParam, lParam));
|
||||
end;
|
||||
|
||||
procedure TForm1.FormCreate(Sender: TObject);
|
||||
begin
|
||||
// Uncomment to monitor WM_KEY* and WM_SYSKEY* messages
|
||||
// Application.OnMessage := AppOnMessage;
|
||||
|
||||
|
||||
hh := SetWindowsHookEx(WH_KEYBOARD_LL, kbdll, GetModuleHandle(nil), 0);
|
||||
end;
|
||||
|
||||
procedure TForm1.FormDestroy(Sender: TObject);
|
||||
begin
|
||||
Form1 := nil;
|
||||
UnhookWindowsHookEx(hh);
|
||||
end;
|
||||
|
||||
procedure TForm1.AppOnMessage(var Msg: TMsg; var Handled: Boolean);
|
||||
begin
|
||||
if (Msg.message = WM_KEYDOWN) or (Msg.message = WM_SYSKEYDOWN) or (Msg.message = WM_KEYUP) or (Msg.message = WM_SYSKEYUP) then
|
||||
begin
|
||||
if Msg.wParam in [VK_CONTROL, VK_SHIFT, VK_MENU] then
|
||||
if (Msg.LParam and $FFFF) = 1 then
|
||||
begin
|
||||
memo1.Lines.Add(Format('%08.8x %08.8x', [Msg.wParam, Msg.lParam]));
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
||||
Loading…
Add table
Reference in a new issue