script1.js:
function run() {
const chromeApp = Application('Google Chrome');
const window = chromeApp.windows[0];
console.log(window.name());
window.bounds = {
x: 0,
y: 0,
width: 500,
height: 500,
};
chromeApp.activate();
}
Run:
osascript -l JavaScript script1.js
And it works
script2.js:
function run() {
const systemEvents = Application('System Events');
const ap = systemEvents.processes().find(ap => ap.name() === 'Google Chrome');
console.log(ap.name());
const window = ap.windows[0];
console.log(window.name());
window.bounds = {
x: 0,
y: 0,
width: 500,
height: 500,
};
}
Run:
osascript -l JavaScript script1.js
It does NOT work:
script2.js: execution error: Error: Error: Can't set that. (-10006)
But I really need to get script2.js work. Because in my real application, I don't know the application name in advance and I need to fetch the process dynamically base on user interaction. Because I don't know application name, I cannot use script1.js.
Any input is appreciate!
function run() {
const systemEvents = Application('System Events');
const p = systemEvents.processes().find(ap => ap.frontmost() === true);
const ap = Application (p.bundleIdentifier());
const window = ap.windows[0];
window.bounds = {
x: 0,
y: 0,
width: 500,
height: 500,
};
}
It’s years since I’ve had the displeasure of using GUI Scripting so didn’t immediately spot the real problem.
The real problem is that System Events is a bloated badly designed mess that shoehorns a dozen libraries’ worth of functionality into one #BigBallOfMud.
SE includes Cocoa Scripting’s Standard Suite window class definition, including a standard bounds property, but doesn’t actually implement it (since SE has no windows of its own). So while SE’s dictionary claims windows elements have a bounds property, trying to get/set that property throws an error. It is very confusing.
Therefore, ignore the window class definition in SE’s Standard Suite, and only look at the window class definition in its Process Suite (aka GUI Scripting). It doesn’t have a bounds property; instead it has separate position and size properties:
position (list of number or missing value) : the position of the window
size (list of number or missing value) : the size of the window
Here is code that works:
tell application "System Events"
set ap to first process whose name is "Firefox"
set win to window 1 of ap
-- set bounds of win to {100, 100, 600, 600} -- this doesn't actually work
set position of win to {100, 100}
set size of win to {500, 500}
end tell
In other words, just because an app’s dictionary says it supports something doesn’t mean it actually does. Trying to figure out an app’s brokenness while also wrestling with JXA’s brokenness is at best a Sisyphean’ exercise.
Figure out how you get your code working in AppleScript first; if it doesn’t work there then you know it’s the app that’s the problem. If you still want to use JXA, then you can attempt to port your working code to that afterwards (although generally I don’t recommend wasting time on JXA as it’s crippled and abandoned, and the whole AppleScript stack is slowly dying anyway).
Inspired by Robert's answer, I found that the following works:
function run() {
const systemEvents = Application('System Events');
const p = systemEvents.processes().find(ap => ap.name() === 'Google Chrome');
console.log(p.name());
const app = Application(p.name());
const window = app.windows[0];
window.bounds = {
x: 0,
y: 0,
width: 500,
height: 500,
};
app.activate();
}
So I can convert a Process into Application by name: const app = Application(p.name()); Then script2.js become script1.js.
But it doesn't always work. For example, the following doesn't work:
function run() {
const systemEvents = Application('System Events');
const p = systemEvents.processes().find(ap => ap.frontmost() === true);
console.log(p.name());
const app = Application(p.name());
const window = app.windows[0];
window.bounds = {
x: 0,
y: 0,
width: 500,
height: 500,
};
app.activate();
}
If the frontmost application is Visual Studio Code. Because the process name will be "Electron" and convert process to application will be wrong.
I'd like to find a way to convert process to application by ID. I am still investigating and I will post updates later.
Update: please check the accepted answer.
You can set the position and size to a two-dimensional array. Because Application("Name of App") doesn't always seem to work, it's best to go through System Events.
let sys = Application("System Events")
let app = sys.applicationProcesses
.whose({ name: "AppYouWant"})[0]
app[0].windows[0].size = [200, 200]
app[0].windows[0].position = [200, 200]
Related
I want to try to build out a simple Electron based macOS app that let's a user drag a file to the app's dock icon and then does something with that file.
However I cannot find a way to drag a file to the dock icon and detect that event. I notice that when I hover over the dock icon with the file it doesn't look like I can drag it there (the icon doesn't go darker as it does with with Safari or Notes).
I followed the official Quick Start guide to create a minimal electron app for starter.
I found little guidance on what a would like to do but came up with the following code that is able to open the window, load my blank index.html but doesn't achieve anything drag related.
main.js
const { app, BrowserWindow } = require("electron");
/* Create and open the App */
app.whenReady().then(() => {
createWindow();
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
titleBarStyle: "hidden",
});
//win.loadURL("https://electronjs.org");
win.loadFile("index.html");
win.show();
process.argv.forEach(handleFile);
console.log("window opened");
console.log(process.argv);
};
/* Drag file unto App icon */
app.on("open-file", handleFile);
app.on("open-url", handleFile);
// You can get the dragged file's path like this
if (process.argv.length >= 2) {
const filePath = process.argv[1];
handleFile(filePath);
}
// If you only allow a single instance of your application to be running
// you should use the 'second-instance' event to handle the case if your app is already running
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
} else {
app.on("second-instance", (event, argv, workingDirectory) => {
// Someone tried to run a second instance
// Handle argv here
if (argv.length >= 2) {
const filePath = argv[1];
handleFile(filePath);
}
});
}
function handleFile(filePath) {
// handle the file as you want
console.log("handleFile", filePath);
}
Thanks for any guidance in a good direction!
Aaron
I would like to use the "zoomToMapObject" method based on a selection on a dropdown menu.
For some reason the start zoom location is the middle of the map and not the set the geoPoint.
(The zooming works but the start location make it look a bit weird.)
My current approach looks like this:
const duration = this.chart.zoomToMapObject(selectedPoloygon, this.countryZoom, true).duration;
setTimeout(() => {
this.chart.homeGeoPoint = geoPoint;
this.chart.homeZoomLevel = this.countryZoom;
}, duration);
this.handleCountrySelection(selectedPoloygon);
Somehow even setting the homeGeoPoint / homeZoomLevel doesn't affect next zoom actions.
**UPDATE: Workaround heavy cost (from 1300 nodes to over 9000) **
I examined the problem a step further. It seems the middle point gets set when I push a new mapImageSeries into the map.
My workarround currently is to draw all points on the map and hide them.
Then after I select a country I change the state to visible.
However this approach is very costly. The DOM-Nodes rises from 1300 to ~ 9100.
My other approach with creating them after a country has been selected AND the zoom animation finished was much more
effective. But due to the map starting every time for a center location it is not viable? Or did I do s.th. wrong?
Here is my current code which is not performant:
// map.ts
export class MapComponent implements AfterViewInit, OnDestroy {
imageSeriesMap = {};
// ... standard map initialization ( not in zone of course )
// creating the "MapImages" which is very costly
this.dataService.getCountries().forEach(country => {
const imageSeriesKey = country.id;
const imageSeriesVal = chart.series.push(new am4maps.MapImageSeries()); // takes arround 1-2 ms -> 300 x 2 ~ 500 ms.
const addressForCountry = this.dataService.filterAddressToCountry(country.id); // returns "DE" or "FR" for example.
const imageSeriesTemplate = imageSeriesVal.mapImages.template;
const circle = imageSeriesTemplate.createChild(am4core.Circle);
circle.radius = 4;
circle.fill = am4core.color(this.colorRed);
circle.stroke = am4core.color('#FFFFFF');
circle.strokeWidth = 2;
circle.nonScaling = true;
circle.tooltipText = '{title}';
imageSeriesTemplate.propertyFields.latitude = 'latitude';
imageSeriesTemplate.propertyFields.longitude = 'longitude';
imageSeriesVal.data = addressForCountry.map(address => {
return {
latitude: Number.parseFloat(address.lat),
longitude: Number.parseFloat(address.long),
title: address.company
};
});
imageSeriesVal.visible = false;
this.imageSeriesMap[imageSeriesKey] = imageSeriesVal;
});
// clicking on the map
onSelect(country) {
this.imageSeriesMap[country].visible = true;
setTimeout( () => {
const chartPolygons = <any>this.chart.series.values[0];
const polygon = chartPolygons.getPolygonById(country);
const anim = this.chart.zoomToMapObject(polygon, 1, true, 1000);
anim.events.on('animationended', () => {});
this.handleCountrySelection(polygon);
}, 100);
});
}
handleCountrySelection(polygon: am4maps.MapPolygon) {
if (this.selectedPolygon && this.selectedPolygon !== polygon) {
this.selectedPolygon.isActive = false;
}
polygon.isActive = true;
const geoPoint: IGeoPoint = {
latitude: polygon.latitude,
longitude: polygon.longitude
};
this.chart.homeGeoPoint = geoPoint;
this.chart.homeZoomLevel = this.countryZoom;
this.selectedPolygon = polygon;
}
}
Thanks to your thorough followup I was able to replicate the issue. The problem you were having is triggered by any one of these steps:
dynamically pushing a MapImageSeries to the chart
dynamically creating a MapImage via data (also please note in the pastebind you provided, data expects an array, I had to change that while testing)
In either step, the chart will fully zoom out as if resetting itself. I'm going to look into why this is happening and if it can be changed, so in the meantime let's see if the workaround below will work for you.
If we only use a single MapImageSeries set in advance (I don't particularly see a reason to have multiple MapImageSeries, would one not do?), that eliminates problem 1 from occurring. Asides from data, we can create() MapImages manually via mapImageSeries.mapImages.create(); then assign their latitude and longitude properties manually, too. With that, problem 2 does not occur either, and we seem to be good.
Here's a demo with a modified version of the pastebin:
https://codepen.io/team/amcharts/pen/c460241b0efe9c8f6ab1746f44d666af
The changes are that the MapImageSeries code is taken out of the createMarkers function so it only happens once:
const mapImageSeries = chart.series.push(new am4maps.MapImageSeries());
const imageSeriesTemplate = mapImageSeries.mapImages.template;
const circle = imageSeriesTemplate.createChild(am4core.Circle);
circle.radius = 10;
circle.fill = am4core.color('#ff0000');
circle.stroke = am4core.color('#FFFFFF');
circle.strokeWidth = 2;
circle.nonScaling = true;
circle.tooltipText = 'hi';
In this case, there's no need to pass chart to createMarkers and return it, so I've passed polygon instead just to demo dynamic latitude/longitudes, I also assign our new MapImage to the polygon's data (dataItem.dataContext) so we can refer to it later. Here's the new body of createMarkers:
function createMarkers(polygon) {
console.log('calling createMarkers');
if ( !polygon.dataItem.dataContext.redDot) {
const dataItem = polygon.dataItem;
// Object notation for making a MapImage
const redDot = mapImageSeries.mapImages.create();
// Note the lat/long are direct properties
redDot.id = `reddot-${dataItem.dataContext.id}`;
// attempt to make a marker in the middle of the country (note how this is inaccurate for US since we're getting the center for a rectangle, but it's not a rectangle)
redDot.latitude = dataItem.north - (dataItem.north - dataItem.south)/2;
redDot.longitude = dataItem.west - (dataItem.west - dataItem.east)/2;;
dataItem.dataContext.redDot = redDot;
}
}
There's no need for the animationended event or anything, it just works since there is no longer anything interfering with your code. You should also have your performance back.
Will this work for you?
Original answer prior to question's edits below:
I am unable to replicate the behavior you mentioned. Also, I don't know what this.countryZoom is.
Just using the following in a button handler...
chart.zoomToMapObject(polygon);
...seems to zoom just fine to the country, regardless of the current map position/zoomLevel.
If you need to time something after the zoom animation has ended, the zoomToMapObject returns an Animation, you can use its 'animationended' event, e.g.
const animation = this.chart.zoomToMapObject(selectedPoloygon, this.countryZoom, true);
animation.events.on("animationended", () => {
// ...
});
Here's an example with all that with 2 external <button>s, one for zooming to USA and the other Brazil:
https://codepen.io/team/amcharts/pen/c1d1151803799c3d8f51afed0c6eb61d
Does this help? If not, could you possibly provide a minimal example so we can replicate the issue you're having?
I just started messing with scala.js and got stuck pretty early. I have no scala experience so I probably overlooked something simple. I try to display an image on the canvas. I tried:
var image:HTMLImageElement = new HTMLImageElement()
image.src = "pathToSource"
image.onload = { () =>
ctx.drawImage(image, 0, 0, 200, 200) //ctx is the canvas rendering context
}
The problem with this code is that onload doesn't seem to exist, even though I can find it here: https://github.com/scala-js/scala-js-dom/blob/master/src/main/scala/org/scalajs/dom/raw/Html.scala#L1333
I also tried a few other methods like onloaddata but I cant figure out what the compiler wants from me:
var image:dom.raw.HTMLImageElement = new HTMLImageElement()
image.src = "/img/image.png"
image.onloadeddata = new js.Function1[Event, _]{
def apply(v1: Event):Unit={
ctx.drawImage(image,0,0,200,200)
}
}
Anyone knows how to load and display an image with scala js?
Thanks in advance!
You have to create the image with dom.document.createElement("img")
and then use onload event on the created image, here is a working example:
val ctx = canvas.getContext("2d")
.asInstanceOf[dom.CanvasRenderingContext2D]
var image = dom.document.createElement("img").asInstanceOf[HTMLImageElement]
image.src = "/img/image.gif"
image.onload = (e: dom.Event) => {
ctx.drawImage(image, 0, 0, 525, 600, 0, 0, 525, 600)
}
Which version of org.scalajs.dom are you using? It looks like onload was added quite recently, in version 0.8.2. That might be the source of your confusion. (I'm on version 0.8.0, and get the same error.)
For reference, the idiomatic Scala syntax for something like this would usually be something like:
image.onloadeddata = { evt:Event =>
ctx.drawImage(image, 0, 0, 200, 200) //ctx is the canvas rendering context
}
or possibly (in some cases, if there is ambiguity):
image.onloadeddata = { evt:Event =>
ctx.drawImage(image, 0, 0, 200, 200) //ctx is the canvas rendering context
}:js.Function1[Event, _]
You're missing an = operator right after .onload.
Can I Set Windows Console width in Node.js?
process.stdout.columns =300;
process.stdout.rows = 300;
console.log(process.stdout.columns)
console.log(process.stdout.rows)
it doesn't work?
it's not very complicated.
var COORD=
refStruct({
X: ref.types.int16
,Y: ref.types.int16
})
//kernel32
this.kernel32 = new ffi.Library('kernel32', {
'SetConsoleScreenBufferSize': ['bool', ['int32', COORD]]
, 'GetStdHandle': ['int32', ['long']]
});
this.setConsoleBufferSize = function (colume,row) {
var handle = winapi.kernel32.GetStdHandle(-11);
var x = winapi.kernel32.SetConsoleScreenBufferSize(handle, new COORD({
X: colume
, Y: row
}));
};
Based on your comments and the documentation for process.stdout I would say that .columns and .rows are read only.
I've been looking for a while and it does not seem like there is any way to resize the console window from node.
Im writing a server to receive key events from an iPhone. I can send a message from the iPhone and have my server display it, on the Mac, now i just need to translate that into a Key press, and simulate that press in Cocoa.
Could anyone offer me a starting point, as i guess this is quite low level.
Thanks
I believe IOHIDPostEvent may be what you're looking for. Something like this:
static void HIDPostVirtualKey(
const UInt8 inVirtualKeyCode,
const Boolean inPostUp,
const Boolean inRepeat)
{
NXEventData event;
IOGPoint loc = { 0, 0 };
bzero(&event, sizeof(NXEventData));
event.key.repeat = inRepeat;
event.key.keyCode = inVirtualKeyCode;
event.key.origCharSet = event.key.charSet = NX_ASCIISET;
event.key.origCharCode = event.key.charCode = 0;
IOHIDPostEvent( get_event_driver(), inPostUp ? NX_KEYUP : NX_KEYDOWN, loc, &event, kNXEventDataVersion, kIOHIDPostHIDManagerEvent, FALSE );
}