Why is Atspi.DeviceEvent data the same after every keystroke event? - vala

I'm really struggling to get AT-SPI to work within a Vala application.
I have it able to notice that a key has been pressed via Atspi.register_keystroke_listener, but can't for the life of me get it to pass anything useful to the callback function. On each key press it returns exactly the same data regardless of the key pressed, and the stroke.event_string never seems to have anything in it.
The following is a stripped down demo application that shows the problem.
public class Demo.Application : Gtk.Application {
private static Application? _app = null;
private Atspi.DeviceListenerCB listener_cb;
private Atspi.DeviceListener listener;
public Application () {
Object (
application_id: "com.bytepixie.snippetpixie",
flags: ApplicationFlags.HANDLES_COMMAND_LINE
);
}
protected override void activate () {
message ("Activated");
Atspi.init();
listener_cb = (Atspi.DeviceListenerCB) on_key_released_event;
listener = new Atspi.DeviceListener ((owned) listener_cb);
try {
Atspi.register_keystroke_listener (listener, null, 0, Atspi.EventType.KEY_RELEASED_EVENT, Atspi.KeyListenerSyncType.ALL_WINDOWS | Atspi.KeyListenerSyncType.CANCONSUME);
} catch (Error e) {
message ("Could not keystroke listener: %s", e.message);
Atspi.exit ();
quit ();
}
}
private bool on_key_released_event (Atspi.DeviceEvent stroke) {
message ("id: %u, hw_code: %d, modifiers: %d, timestamp: %u, event_string: %s, is_text: %s",
stroke.id,
stroke.hw_code,
stroke.modifiers,
stroke.timestamp,
stroke.event_string,
stroke.is_text.to_string ()
);
return false;
}
public override int command_line (ApplicationCommandLine command_line) {
hold ();
activate ();
return 0;
}
public static new Application get_default () {
if (_app == null) {
_app = new Application ();
}
return _app;
}
public static int main (string[] args) {
var app = get_default ();
return app.run (args);
}
}
When compiled and run, and then the keys "qwerty" are pressed, I get the following.
ian#ians-apollo:~/Documents/atspi-test$ valac demo.vala --pkg gtk+-3.0 --pkg atspi-2
ian#ians-apollo:~/Documents/atspi-test$ ./demo
** Message: 18:35:59.373: demo.vala:15: Activated
(demo:18257): GLib-GObject-CRITICAL **: 18:35:59.456: g_object_unref: assertion 'G_IS_OBJECT (object)' failed
** Message: 18:36:00.716: demo.vala:32: id: 22029, hw_code: 4, modifiers: 0, timestamp: 0, event_string: (null), is_text: true
q** Message: 18:36:01.046: demo.vala:32: id: 22029, hw_code: 4, modifiers: 0, timestamp: 0, event_string: (null), is_text: true
w** Message: 18:36:01.477: demo.vala:32: id: 22029, hw_code: 4, modifiers: 0, timestamp: 0, event_string: (null), is_text: true
e** Message: 18:36:01.837: demo.vala:32: id: 22029, hw_code: 4, modifiers: 0, timestamp: 0, event_string: (null), is_text: true
r** Message: 18:36:02.187: demo.vala:32: id: 22029, hw_code: 4, modifiers: 0, timestamp: 0, event_string: (null), is_text: true
t** Message: 18:36:02.583: demo.vala:32: id: 22029, hw_code: 4, modifiers: 0, timestamp: 0, event_string: (null), is_text: true
y** Message: 18:36:10.587: demo.vala:32: id: 22029, hw_code: 4, modifiers: 0, timestamp: 0, event_string: (null), is_text: true
You can see the "qwerty" at the beginning of each line in the console as I'm not consuming the key strokes, but there's no difference in the data output each time.
What am I missing? Is there some sort of caching going on that needs to be cleared after each event?

It took a while to figure this one out and the demo was very helpful. In essence the C function signature for the callback is the wrong way around.
Reading the C documentation for AtspiDeviceListenerCB the function signature should be:
gboolean
(*AtspiDeviceListenerCB) (const AtspiDeviceEvent *stroke,
void *user_data);
The user_data is after stroke.
In the example Vala program, on_key_released_event is a method of Demo.Application. Vala will place the instance reference as the first parameter of the method in the generated C. Using the --ccode switch with valac shows the following in the generated C:
static gboolean demo_application_on_key_released_event (DemoApplication* self,
AtspiDeviceEvent* stroke);
The solution is to tell the Vala compiler to place the instance reference at a different position. In the example program this means changing:
private bool on_key_released_event (Atspi.DeviceEvent stroke) {
to
[CCode (instance_pos = -1)]
private bool on_key_released_event (Atspi.DeviceEvent stroke) {
The CCode attribute detail instance_pos could be another value, but -1 places the instance parameter as the last parameter in the function signature. We could have used 2 instead. For more on changing the position of generated C function arguments see the Vala Writing Bindings Manually document.
Another solution would have been not to use the instance data at all and use DeviceListener.simple instead.
It would be nice to think there is enough information available to the Vala compiler to work out that an object's method that is used as a callback should have the instance parameter in a different position in the generated C. I've not taken the time to investigate that possibility though.

Related

CMVideoFormatDescriptionCreateFromH264ParameterSets throw Initialization of 'UnsafePointer<Int>' results in a dangling pointer

So previously I opened Initialization of 'UnsafePointer<Int>' results in a dangling pointer but the mods said it's a duplicate one.
However I don't think this is a duplicate one, as the links being referred in that post cannot easily direct to a solution. Thus I have to open a new one after 3 days. I tried what is answered from #bscothern in the previous post, but more errors come.
The code being used currently is like below from #bscothern, and it still throws
Generic parameter 'R' could not be inferred
var formatDesc: CMVideoFormatDescription?
func createH264FormatDescription(SPS: Array<UInt8>, PPS: Array<UInt8>) -> OSStatus {
if formatDesc != nil { formatDesc = nil }
let status = SPS.withUnsafeBufferPointer { SPS in
PPS.withUnsafeBufferPointer { PPS in
let paramSet = [SPS.baseAddress!, PPS.baseAddress!]
let paramSizes = [SPS.count, PPS.count]
return paramSet.withUnsafeBufferPointer { paramSet in
paramSizes.withUnsafeBufferPointer { paramSizes in
CMVideoFormatDescriptionCreateFromH264ParameterSets(allocator: kCFAllocatorDefault, parameterSetCount: 2, parameterSetPointers: paramSet.baseAddress!, parameterSetSizes: paramSizes.baseAddress!, nalUnitHeaderLength: 4, formatDescriptionOut: &formatDesc)
}
}
}
}
return status
}
Original question from previous post:
So I have some code to create H264ParameterSets like:
var formatDesc: CMVideoFormatDescription?
func createH264FormatDescription(SPS: Array<UInt8>, PPS: Array<UInt8>) -> OSStatus {
if formatDesc != nil { formatDesc = nil }
let paramSet = [UnsafePointer<UInt8>(SPS), UnsafePointer<UInt8>(PPS)]
let paramPointers = UnsafePointer<UnsafePointer<UInt8>>(paramSet)
let paramSizes = UnsafePointer<Int>([SPS.count, PPS.count])
let status = CMVideoFormatDescriptionCreateFromH264ParameterSets(allocator: kCFAllocatorDefault, parameterSetCount: 2, parameterSetPointers: paramPointers, parameterSetSizes: paramSizes, nalUnitHeaderLength: 4, formatDescriptionOut: &formatDesc)
return status
}
Starting on Xcode 11.4 I got warnings for those UnsafePointer(), which seems not happen before:
Initialization of UnsafePointer<UInt8> results in a dangling pointer
Initialization of UnsafePointer<UnsafePointer<UInt8>> results in a dangling pointer
Initialization of UnsafePointer<Int> results in a dangling pointer
I'm not sure why we see this? and how can I remove the warning? Thank in advance.
When you find some error message like: Generic parameter 'R' could not be inferred, there may be two possible reasons.
There is some type-related error somewhere, so Swift cannot infer the actual type R in that context
The expression is a little more complex than Swift can infer the type
In case 1, you need to find where is the actual error causing the issue.
In case 2, you can add some explicit type annotations to help Swift infer the types.
Please try something like this:
var formatDesc: CMVideoFormatDescription?
func createH264FormatDescription(SPS sps: Array<UInt8>, PPS pps: Array<UInt8>) -> OSStatus {
if formatDesc != nil { formatDesc = nil }
let status = sps.withUnsafeBufferPointer { spsBP->OSStatus in //<- Specify return type explicitly.
pps.withUnsafeBufferPointer { ppsBP in
let paramSet = [spsBP.baseAddress!, ppsBP.baseAddress!]
let paramSizes = [spsBP.count, ppsBP.count]
return paramSet.withUnsafeBufferPointer { paramSetBP in
paramSizes.withUnsafeBufferPointer { paramSizesBP in
CMVideoFormatDescriptionCreateFromH264ParameterSets(allocator: kCFAllocatorDefault, parameterSetCount: 2, parameterSetPointers: paramSetBP.baseAddress!, parameterSetSizes: paramSizesBP.baseAddress!, nalUnitHeaderLength: 4, formatDescriptionOut: &formatDesc)
}
}
}
}
return status
}

Neo4j/GraphQL augmented Schema

I'm trying to learn a bit about Neo4j's GraphQL integration. I'm using the GrandStack starter which can be found here. Grand Stack Starter. The starter doesn't use a lot of the boilerplate that you see in other GrapQL applications because, as I understand it, the Neo4j integration is supposed to bypass the need to double declare Schema and Resolvers. It uses Apollo Server and an "augmentedSchema" instead. Using the starter code I've tried to add some mutations to the schema, in this case, a deleteUser mutation. I keep getting an error, saying that the Mutation id defined more than once, when I know I'm only putting it in the code in one place. This is my schema.js:
import { neo4jgraphql } from "neo4j-graphql-js";
export const typeDefs = `
type User {
id: ID!
name: String
friends(first: Int = 10, offset: Int = 0): [User] #relation(name: "FRIENDS", direction: "BOTH")
reviews(first: Int = 10, offset: Int = 0): [Review] #relation(name: "WROTE", direction: "OUT")
avgStars: Float #cypher(statement: "MATCH (this)-[:WROTE]->(r:Review) RETURN toFloat(avg(r.stars))")
numReviews: Int #cypher(statement: "MATCH (this)-[:WROTE]->(r:Review) RETURN COUNT(r)")
}
type Business {
id: ID!
name: String
address: String
city: String
state: String
reviews(first: Int = 10, offset: Int = 0): [Review] #relation(name: "REVIEWS", direction: "IN")
categories(first: Int = 10, offset: Int =0): [Category] #relation(name: "IN_CATEGORY", direction: "OUT")
}
type Review {
id: ID!
stars: Int
text: String
business: Business #relation(name: "REVIEWS", direction: "OUT")
user: User #relation(name: "WROTE", direction: "IN")
}
type Category {
name: ID!
businesses(first: Int = 10, offset: Int = 0): [Business] #relation(name: "IN_CATEGORY", direction: "IN")
}
type Query {
users(id: ID, name: String, first: Int = 10, offset: Int = 0): [User]
businesses(id: ID, name: String, first: Int = 10, offset: Int = 0): [Business]
reviews(id: ID, stars: Int, first: Int = 10, offset: Int = 0): [Review]
category(name: ID!): Category
usersBySubstring(substring: String, first: Int = 10, offset: Int = 0): [User] #cypher(statement: "MATCH (u:User) WHERE u.name CONTAINS $substring RETURN u")
}
type Mutation {
deleteUser(id: ID!): User
}
`;
export const resolvers = {
Query: {
users: neo4jgraphql,
businesses: neo4jgraphql,
reviews: neo4jgraphql,
category: neo4jgraphql,
usersBySubstring: neo4jgraphql
},
Mutation: {
deleteUser: neo4jgraphql
}
};
Here's my index:
import { typeDefs, resolvers } from "./graphql-schema";
import { ApolloServer, gql, makeExecutableSchema } from "apollo-server";
import { v1 as neo4j } from "neo4j-driver";
import { augmentSchema } from "neo4j-graphql-js";
import dotenv from "dotenv";
dotenv.config();
const schema = makeExecutableSchema({
typeDefs,
resolvers
});
// augmentSchema will add autogenerated mutations based on types in schema
const augmentedSchema = augmentSchema(schema);
const driver = neo4j.driver(
process.env.NEO4J_URI || "bolt://localhost:7687",
neo4j.auth.basic(
process.env.NEO4J_USER || "neo4j",
process.env.NEO4J_PASSWORD || "neo4j"
)
);
if (driver){
console.log("Database Connected")
} else{
console.log("Database Connection Error")
}
const server = new ApolloServer({
// using augmentedSchema (executable GraphQLSchemaObject) instead of typeDefs and resolvers
//typeDefs,
//resolvers,
context: { driver },
// remove schema and uncomment typeDefs and resolvers above to use original (unaugmented) schema
schema: augmentedSchema
});
server.listen(process.env.GRAPHQL_LISTEN_PORT, '0.0.0.0').then(({ url }) => {
console.log(`GraphQL API ready at ${url}`);
});
Error Message:
No other code from the starter has been changed. I haven't been able to find much info on the augmentedSchema in the docs. If anyone can point me in the right direction I would appreciate it.
The issue was caused by the augmentedSchema, which automatically creates default mutations for each type i.e. in this case it had already created delete user, update user, and add user. You can see this when you run the GraphiQL server. So I was double declaring the delete user mutation.

Reactor GroupedFlux - wait to complete

Having an async publisher like the one bellow, is there a way with Project Reactor to wait till the entire stream is finished processing?
Of course, without having to add a sleep for an unknown duration...
#Test
public void groupByPublishOn() throws InterruptedException {
UnicastProcessor<Integer> processor = UnicastProcessor.create();
List<Integer> results = new ArrayList<>();
Flux<Flux<Integer>> groupPublisher = processor.publish(1)
.autoConnect()
.groupBy(i -> i % 2)
.map(group -> group.publishOn(Schedulers.parallel()));
groupPublisher.log()
.subscribe(g -> g.log()
.subscribe(results::add));
List<Integer> input = Arrays.asList(1, 3, 5, 2, 4, 6, 11, 12, 13);
input.forEach(processor::onNext);
processor.onComplete();
Thread.sleep(500);
Assert.assertTrue(results.size() == input.size());
}
You can replace these lines:
groupPublisher.log()
.subscribe(g -> g.log()
.subscribe(results::add));
with this
groupPublisher.log()
.flatMap(g -> g.log()
.doOnNext(results::add)
)
.blockLast();
flatMap is a better pattern than subscribe-within-subscribe and will take care of subscribing to the group for you.
doOnNext takes care of the consuming side-effect (adding values to the collection), freeing you up from the need to perform that in the subscription.
blockLast() replaces the subscription, and rather than letting you provide handlers for the events it blocks until the completion (and returns the last emitted item, but you would already have taken care of that within doOnNext).
The main problem to use blockLast() is that you will never release your pipeline if your operation are not able to finish.
What you need to do is get the Disposable and check if has finish the pipeline which means the boolean isDisposed it will return true.
Then it´s up to you to decide if you want to have a timeout, like the lazy count implementation :)
int count = 0;
#Test
public void checkIfItDisposable() throws InterruptedException {
Disposable subscribe = Flux.just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.map(number -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return number;
}).subscribeOn(Schedulers.newElastic("1"))
.subscribe();
while (!subscribe.isDisposed() && count < 100) {
Thread.sleep(400);
count++;
System.out.println("Waiting......");
}
System.out.println("It disposable:" + subscribe.isDisposed());
And in case you want to use blockLast, at least add a timeout
#Test
public void checkIfItDisposableBlocking() throws InterruptedException {
Flux.just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.map(number -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return number;
}).subscribeOn(Schedulers.newElastic("1"))
.blockLast(Duration.of(60, ChronoUnit.SECONDS));
System.out.println("It disposable");
}
You can see more Reactor examples here if you need more ides https://github.com/politrons/reactive

Swift compiler error: "Cannot invoke 'NSLog' with an argument list of type '(String, [CVarArgType])'" [duplicate]

I have the following code:
public static func e(file: String = __FILE__,
function: String = __FUNCTION__,
line: Int = __LINE__,format: String, args: CVarArgType...)
{
NSLog([%d]\t [%#] " + format,line,function, args); //<<< I have no idea how to pass the params here
}
I get compiler error on NSLog that can not be called as I did.
Simply I need to print the var args, function name and line using a single NSLOG call.
Similar as in Swift function with args... pass to another function with args,
you have to create a CVaListPointer (the Swift equivalent of va_list in C) and call a function which takes a CVaListPointer parameter,
in this case NSLogv().
public class Logger {
public static func e(
format: String,
file: String = __FILE__,
function: String = __FUNCTION__,
line: Int = __LINE__,
args: CVarArgType...)
{
let extendedFormat = "[%d]\t [%#] " + format
let extendedArgs : [CVarArgType] = [ line, function ] + args
withVaList(extendedArgs) { NSLogv(extendedFormat, $0) }
}
}
// Example usage:
Logger.e("x = %d, y = %f, z = %#", args: 13, 1.23, "foo")
I have made format the first parameter, so that is has no external
parameter name. The variable argument list must be the last parameter
in Swift 1.2, and the external parameter cannot be avoided in this
combination with default parameters.
In Swift 2 you can avoid the external parameter name for the
variable arguments:
public class Logger {
public static func e(
format: String,
_ args: CVarArgType...,
file: String = __FILE__,
function: String = __FUNCTION__,
line: Int = __LINE__)
{
let extendedFormat = "[%d]\t [%#] " + format
let extendedArgs : [CVarArgType] = [ line, function ] + args
withVaList(extendedArgs) { NSLogv(extendedFormat, $0) }
}
}
// Example usage:
Logger.e("x = %d, y = %f, z = %#", 13, 1.23, "foo")

Simulate drop event with a file

Users upload images to this form by dropping a file on it.
Is there a way in to trigger drop with a File/Blob object programmatically? Something like MouseEvent.initMouseEvent() https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/initMouseEvent
but like initDropEvent?
I am researching here:
https://dxr.mozilla.org/mozilla-central/source/obj-x86_64-unknown-linux-gnu/_tests/testing/mochitest/mochijar/chrome/mochikit/content/tests/SimpleTest/ChromeUtils.js?offset=200#254
https://dxr.mozilla.org/mozilla-central/source/browser/base/content/test/newtab/head.js?offset=0#690
https://dxr.mozilla.org/mozilla-central/source/browser/base/content/test/newtab/browser_newtab_bug765628.js#25
https://dxr.mozilla.org/mozilla-central/source/testing/mochitest/tests/SimpleTest/ChromeUtils.js#179 ----- this one is a mochitest and code from mochitests never ever work for me, so i think this one is useless as it needs some special environment i think
Based on this I ran this code from scratchpad witha tab with twitter loaded, and aftre clicking the "tweet" button:
// https://dxr.mozilla.org/mozilla-central/source/browser/base/content/test/newtab/head.js?offset=100#690
//https://dxr.mozilla.org/mozilla-central/source/browser/base/content/test/newtab/browser_newtab_bug765628.js#21
/**
* Creates a custom drag event.
* #param aEventType The drag event's type.
* #param aData The event's drag data (optional).
* #return The drag event.
*/
function createDragEvent(aContentWindow, aEventType, aData, aDataType) {
// aDataType text/x-moz-url, text/plain, etc
let dataTransfer = new (aContentWindow).DataTransfer("dragstart", false);
dataTransfer.mozSetDataAt(aDataType, aData, 0);
let event = aContentWindow.document.createEvent("DragEvents");
event.initDragEvent(aEventType, true, true, aContentWindow, 0, 0, 0, 0, 0,
false, false, false, false, 0, null, dataTransfer);
return event;
}
function sendTwitterDropEvent() {
var aContentWindow = gBrowser.contentWindow;
var aContentDocument = aContentWindow.document;
var btnNewTweet = aContentDocument.getElementById('global-new-tweet-button');
console.info('btnNewTweet:', btnNewTweet);
if (!btnNewTweet) {
throw new Error('global tweet button not found, probably not logged in');
}
btnNewTweet.click();
var inputAddPhoto = aContentDocument.getElementById('global-tweet-dialog').querySelector('input[type=file]');
if (!inputAddPhoto) {
throw new Error('add photo button not found! i have no idea what could cause this');
}
var formTweet = aContentDocument.getElementById('global-tweet-dialog-dialog').querySelector('form'); //<form.t1-form.tweet-form has-preview has-thumbnail dynamic-photos photo-square-4>
if (!formTweet) {
throw new Error('tweet form not found! i have no idea what could cause this');
}
console.info('formTweet:', (formTweet instanceof Ci.nsIDOMNode));
var ifaceReq = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor);
var windowUtils = ifaceReq.getInterface(Ci.nsIDOMWindowUtils);
var aDragData = 'site 99';
var aDragDataType = 'plain/text';
var event = createDragEvent(aContentWindow, "drop", aDragData, aDragDataType);
windowUtils.dispatchDOMEventViaPresShell(formTweet, event, true);
}
sendTwitterDropEvent();
This should populate the tweet input with "site 99" but it doesnt. I was testing with plain text then after I get that working I was thinking of moving to File/Blob.
Simplified non-twitter test case
Open tab with this: data:text/html,<input id=rawr>
Open scratchpad and run this code from scratchpad:
function createDragEvent(aContentWindow, aEventType, aData, aDataType) {
// aDataType text/x-moz-url, text/plain, etc
let dataTransfer = new (aContentWindow).DataTransfer("dragstart", false);
dataTransfer.mozSetDataAt(aDataType, aData, 0);
let event = aContentWindow.document.createEvent("DragEvents");
event.initDragEvent(aEventType, true, true, aContentWindow, 0, 0, 0, 0, 0,
false, false, false, false, 0, null, dataTransfer);
return event;
}
function sendTwitterDropEvent() {
var aContentWindow = gBrowser.contentWindow;
var aContentDocument = aContentWindow.document;
var formTweet = aContentDocument.getElementById('rawr');
if (!formTweet) {
throw new Error('tweet form not found! i have no idea what could cause this');
}
console.info('formTweet:', (formTweet instanceof Ci.nsIDOMNode), formTweet);
var ifaceReq = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor);
var windowUtils = ifaceReq.getInterface(Ci.nsIDOMWindowUtils);
var aDragData = 'site 99';
var aDragDataType = 'plain/text';
var event = createDragEvent(aContentWindow, "drop", aDragData, aDragDataType);
var rezDrop = windowUtils.dispatchDOMEventViaPresShell(formTweet, event, true);
console.info('rezDrop:', rezDrop);
}
sendTwitterDropEvent();

Resources