
Управление мышью ПК посредством мобильного на Air и ANE

Привет друзья! Это уже наверное 3я редакция данной статьи :) Каждый раз находя в ней ошибки и логические нестыковки — все дальше и дальше уходило желание в её публикации :)
Предыстория:
На работе клиенту понадобилось двигать мышку на его PC под управлением Windows7 удаленно с помощью iPad. Удаленно — в нашем случае было по внутренней сети WiFi. А администратор надохится в 4х метрах перед громадным экраном :) Причем тут флеш? Всё просто. Сделал я это с помощью Native Extension и Adobe Air. Я не обладаю глубокими знаниями ни Objective-C ни С++. Собрать HelloWorld — это не знания :) По-этому, вопрос о 100% нативном решении даже не рассматривался. Откуда я узнал как нажать виртуально мышку и сместить курсор — ответ есть в гугл и msdn ) Все гуглится за пару минут.
Попробовать — в конце статьи.
Сразу хочу сообщить о некоторых багах. Учитывая, что я не С разработчик — некоторые вещи могли бы быть сделаны иначе и было бы здорово в камментах увидеть грамотные советы. Баг заключается в том, что существует пару программ, которые частично нельзя контролировать с помощью этой виртуальной мышки (свернуть окно, перетащить за title bar, закрыть окно). Но все остальное — работает. Ради любопытства я смог с отключенной от USB мышкой полностью управлять компьютером.
Что касается метода смещения — специально сделал так, чтоб курсор ездил через дельту. Чтоб не прыгал в разные углы, а ездил как будто мы крутим старый добрый trackball :)
И так! Шаг номер ноль, который должен делать каждый разработчик под Air — скачать последний SDK labs.adobe.com/technologies/flashruntimes/air/ для своей платформы. На момент написания — это 4.0.0.1170
Первое, что нам надо — это скачать MiniGW sourceforge.net/projects/mingw/files/latest/download?source=files (официальный последний инсталлер). Запускаем и ставим его.
Когда у нас дойдет до выбора пакетов, которые мы хотим — отмечаем:
— minigw32-base
— minigw32-gcc-g++
После отметки — заходим в контекстное меню Installation > Apply Changes

MiniGW — это набор всяких компиляторов :) (Не буду расстраивать слэнгом, который я сам не понимаю до конца). Рекомендую не испытывать свои нервы и ставить все по умолчанию в C:\MiniGW
Второй шаг — необходимо прописать PATH переменную со ссылкой на C:\MiniGW\bin. Для этого открываем «Выполнить» ( Win+R) и вбиваем туда строку rundll32 sysdm.cpl,EditEnvironmentVariables

Открылось окошко… Во второй вкладке ищем Path вкладку и жмем «Изменить».

Все значения разделены через точку с запятой. Вбиваем туда ;C:\MinGW\bin\ и следом, чтоб 2 раза не заходить — ;C:\Program Files\Adobe\Air Flash Runtimes\4.0.0.1170\. Мы вписываем сюда ссылку на папку с Air SDK, т.к. в дальнейшем будем обращаться к adt из ком. строки.
В итоге вся строка стала такая ;C:\MinGW\bin\;C:\Program Files\Adobe\Air Flash Runtimes\4.0.0.1170\. Жмем ок, закрываем, применяем. Заходим снова в Выполнить и открываем cmd.
набиваем затем adt -version. Если вернулась версия — значит все подтянулось. Теперь пишем там же gcc и тоже жмем ентер. Любая ошибка кроме «файл не найнед» — хорошо. Значит работает.
Третий шаг — самый увлекательный! Это написание нативного расширения! Уверяю Вас! Я не знаю С на уровне, чтоб мог умничать. Там пару примеров посмотрел, там подсмотрел… :) Все проще до нельзя.
1) Создаем папку C:\MouseExtension\ и в ней два файла MousePosition.h и MousePosition.c.
2) Идем в Air SDK C:\Program Files\Adobe\Air Flash Runtimes\4.0.0.1170\include, берем файл FlashRuntimeExtensions.h и копируем рядом с файлами из предыдущего пункта
3) Создаем файл Compile++.bat. В итоге у нас получилось 4 файла в папке, как на рисунке ниже:

Теперь переходим от простого к сложному. В Compile++.bat пишем:
gcc -O3 -Wall -c -fmessage-length=0 -o MousePosition.o MousePosition.c
gcc -shared -o MousePosition.dll MousePosition.o "C:\Program Files\Adobe\Air Flash Runtimes\4.0.0.1170\lib\win\FlashRuntimeExtensions.lib"
pause
Что значат все эти строки я не до конца понимаю. Подсмотрел их в FB компиляторе под C++, когда писал этот урок. Просто изначально не у всех он стоит. А из строки как-то быстрее будет.
Во второй строке надо прописать путь с кавычками на либу FlashRuntimeExtensions.lib. В переводе на флешовый язык — это нечто вроде SWC ) Только для компилятора C/C++
Пауза нужна, чтоб увидеть результат сборки.
Открываем FlashRuntimeExtensions.h и удаляем в заголовке файла:
#ifdef WIN32
typedef unsigned __int32 uint32_t;
typedef unsigned __int8 uint8_t;
typedef __int32 int32_t;
#else
#include <stdint.h>
#endif
и вместо этого оставляем только #include <stdint.h>

Теперь переходим к написанию самого главного кода! :)
Открываем MousePosition.h и пишем туда код. Я вбил максимально возможное число камментов:
#ifndef MOUSEPOSITION_H_
#define MOUSEPOSITION_H_
#include "FlashRuntimeExtensions.h"
//
// Сообщаем компилятору, что данные методы будут работать "наружу". Нечто вроде public static в AS3
// В коде есть initializer и finalier это два метода, которые обрабатываются из Adobe Air
// У нас могут быть 50 разных Native Extensions. И чтоб они не мешали друг другу жить - давайте
// переименуем названия этих 2х методов на:
// com_therabbit_utils_pc_initializer
// com_therabbit_utils_pc_finalizer
//
__declspec(dllexport) void com_therabbit_utils_pc_initializer(void** extData, FREContextInitializer* ctxInitializer, FREContextFinalizer* ctxFinalier);
__declspec(dllexport) void com_therabbit_utils_pc_finalizer(void* extData);
#endif /* MOUSEPOSITION_H_ */
Открываем MousePosition.c. Когда я начинал только ковыряться во всем этом — держался за голову. Естественная реакция на новые грабли :) Но все оказалось просто. Слишком просто!
#include "FlashRuntimeExtensions.h"
#include "MousePosition.h"
#include <windows.h>
#include <winuser.h>
// Глобальная переменная currentPos. В сях мы слева пишем тип объекта и справа название переменной. Т.е. POINT currentPos; это var currentPos : Point; на as3
POINT currentPos;
FREObject leftButton(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[])
{
uint32_t isDownValue;
// FREGetObjectAsBool - это "Flash Runtime Get Object as Bool". Список возможностей можно найти в интернете. На сегодня достаточно популярно описано как с этим работать.
// Когда мы присылаем параметры из флеша - они попадают в массим argv. Справа мы пишем указатель на переменную, куда будет отправлен объект их параметра с помощью метода FREGetObjectAsBool
FREGetObjectAsBool(argv[0], &isDownValue);
// mouse_event это WinAPI метод ) ничего не придумано мной. Читаем MSDN.
if ( isDownValue == 1 ) {
mouse_event(MOUSEEVENTF_LEFTDOWN, currentPos.x,currentPos.y,0,0);
} else {
mouse_event(MOUSEEVENTF_LEFTUP, currentPos.x,currentPos.y,0,0);
}
// Очень важно всегда возвращать что-либо. Если у нас код ничего не возвращает - отправляем NULL. Это техническое условие использования Native Extensions
return NULL;
}
FREObject rightButton(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[])
{
uint32_t isDownValue;
FREGetObjectAsBool(argv[0], &isDownValue);
if ( isDownValue == 1 ) {
mouse_event(MOUSEEVENTF_RIGHTDOWN, currentPos.x,currentPos.y,0,0);
} else {
mouse_event(MOUSEEVENTF_RIGHTUP, currentPos.x,currentPos.y,0,0);
}
return NULL;
}
FREObject setMousePosition(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[])
{
GetCursorPos(&currentPos);
int32_t x,y;
FREGetObjectAsInt32(argv[0], &x);
FREGetObjectAsInt32(argv[1], &y);
currentPos.x += x;
currentPos.y += y;
SetCursorPos(currentPos.x, currentPos.y);
return NULL;
}
void contextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctions, const FRENamedFunction** functions) {
*numFunctions = 3; // Количеству функций, доступных для обработки
FRENamedFunction* func = (FRENamedFunction*) malloc(sizeof(FRENamedFunction) * (*numFunctions));
func[0].name = (const uint8_t*) "setMousePosition"; // Строковое название. Это мы вызываем из AS3 в коде context.call("setMousePosition");
func[0].functionData = NULL;
func[0].function = &setMousePosition; // А это ссылка на функцию уже внутри этого файла. Указатель & ссылается на функцию
// Ниже по шаблону
func[1].name = (const uint8_t*) "leftButton";
func[1].functionData = NULL;
func[1].function = &leftButton;
func[2].name = (const uint8_t*) "rightButton";
func[2].functionData = NULL;
func[2].function = &rightButton;
*functions = func;
}
void contextFinalizer(FREContext ctx) {
return;
}
void comtherabbitutilspcinitializer(void** extData, FREContextInitializer* ctxInitializer, FREContextFinalizer* ctxFinalizer){
*ctxInitializer = &contextInitializer;
*ctxFinalizer = &contextFinalizer;
}
void comtherabbitutilspcfinalizer(void* extData) {
return;
}
Сохраняем все и запускаем Compile++.bat. В результате видим:

Это значит, что ошибок нет и все прошло хорошо. В знак успеха у нас появились файлы MousePosition.dll и MousePosition.o. Второй можем убить. Он нам не нужен :)
Теперь переходим ко второй части нашего балета! Создаем тут же descriptor.xml с текстом:
<?xml version="1.0" encoding="UTF-8"?>
<extension xmlns="http://ns.adobe.com/air/extension/4.0">
<id>com.therabbit.utils.pc.setMousePosition</id>
<versionNumber>1.0.0</versionNumber>
<platforms>
<platform name="Windows-x86">
<applicationDeployment>
<nativeLibrary>MousePosition.dll</nativeLibrary>
<initializer>com_therabbit_utils_pc_initializer</initializer>
<finalizer>com_therabbit_utils_pc_finalizer</finalizer>
</applicationDeployment>
</platform>
</platforms>
</extension>
Из кода следует, что работать должно на Windows-x86 (на 64 тоже работает). Вход и выход в нативные методы описан в initializer и finalizer.
В id у нас указан идентификатор, который будет определятся Air и по нему подбирать нужный Native Extension. Естественно, что все должно быть описано уникально :)
Теперь делаем SWC файл, который будет связывать наш будущий ANE с Air.
Сегодня с помощью abyss я пытался поднять компиляцию SWC в FD и у меня не выдержали нервы. У него все работает — у меня нет! :) FD писали индусы однозначно. Чтоб что-то собрать — надо там скачать, тут подкрутить и там подмазать. Это отбило желание открывать FD второй раз и я его снес. Я не собираюсь пол компьютера перенастраивать, чтоб собрать несчастных 10 строк. Flash IDE в 2 клика это делает… Ну да ладно, простите ) Импульсивный в последнее время :)
Пишем туда код. Обязательно в параметрах компиляции укажите версию swf -swf-version 19. Можно от 13 до 23 (последняя). Я пишу 19 пока что.
package com.therabbit.utils.pc
{
/**
* ...
* @author TheRabbit
*/
import flash.external.ExtensionContext;
public final class MousePosition
{
private static var context : ExtensionContext;
public static function setMousePosition(x:int,y:int):void
{
if ( !context ) {
context = ExtensionContext.createExtensionContext("com.therabbit.utils.pc.setMousePosition", null);
}
context.call("setMousePosition",x,y);
}
public static function leftButton(isDown:Boolean):void{
if ( context ) {
context.call("leftButton",isDown);
}
}
public static function rightButton(isDown:Boolean):void{
if ( context ) {
context.call("rightButton",isDown);
}
}
public static function dispose():void{
if ( context ) {
// Создаем dispose метод, убивающий связь с расширением
context.dispose();
}
}
}
}
Может быть кто-то видел стандартный метод isSupported в ANE-шках? Так вот :) Считаю это лишним. У нас гарантированно это будет работать и я еще ни разу не видел обратного. Тут объясню лишь строку ExtensionContext.createExtensionContext(«com.therabbit.utils.pc.setMousePosition», null);
Мы передаем строкой не пакет as3, а строку по которой Air будет у себя внутри искать ID из всех descriptor.xml. Помните, что я писал про 50 анешек? Это касается как id, так и initializer/finalizer
И теперь непосредственное общение с DLL — context.call(«setMousePosition»,x,y);
Метод setMousePosition внутри DLL мы указывали через &setMousePosition и то, что в самом public static function setMousePosition описано — просто для удобства. Можно и так написать:
public static function <u>любое название</u>(x:int,y:int):void
{
if ( !context ) {
context = ExtensionContext.createExtensionContext("com.therabbit.utils.pc.setMousePosition", null);
}
context.call("setMousePosition",x,y);
}
Главное с context.call не ошибиться :)
В общем, собрали SWC и положили рядом с DLLкой. Дальше обязательно из SWC (это обычный zip) вытягиваем library.swf файл и тоже кладем рядом. На выходе у нас такой список файлов:

Мы почти у цели! Создаем GenerateANE.bat с текстом:
adt -package -target ane MousePosition.ane descriptor.xml -swc MousePosition.swc -platform Windows-x86 library.swf MousePosition.dll
Запускаем… барабанная дробь и… не работает :) Быстро пройдясь углам своего мозга — понимаем, что нижние подчеркивания у initializer и finalizer не считываются (у нас было com_therabbit_utils_pc_initializer и com_therabbit_utils_pc_finalizer). Кстати, кто-то может сказать, что я знал заранее — это не так. Просто написал a и оно собрало, а a_a :) Вывод очевиден!
Хотя раньше и нижнее подчеркивание работало. Ну да ладно! Возвращаемся в файлы MousePosition.c и MousePosition.h и просто убираем там нижние подчеркивания в этих строках :)
Конечно comtherabbitutilspcinitializer и comtherabbitutilspcfinalizer я называю «выколи глаз», но зато работает :P. Быстренько убрав _ и запустив Compile++.bat — переходим к запуску GenerateANE.bat и… и… остальное за $0.99 Шутка. Так же не забудьте в XML прописать без нижнего подчеркивания!
На выходе у нас долгожданный MousePosition.ane

Сказать честно — 40% описаного тут у меня давно создается генераторами. Всякие извлечения из swc и т.д. Даже в C части кода — шаблонизаторы (я их так называю) использую и нет надобности писать 5 км одного и того же кода.
Теперь напишем iOS клиента и микро-сервер. Так замечательно случилось, что по щучьему велению в iOS был добавлен UDP в начале лета :) Это значит, что мы можем слать более «быстрые» пакеты, чем через TCP…
Накидаем такую форму во флеше:
1) два текстовых полня Input с именами IP и PORT
2) кнопка connect с именем ConnectButton
3) две кнопки buttonLeft и buttonRight
4) самый нижный элемент с именем pad. Именно по нему и будем возить мышкой

И напишем код для Adobe Air проекта под iOS и соберем нашу IPA
package {
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.net.DatagramSocket;
import flash.events.DatagramSocketDataEvent;
import flash.utils.ByteArray;
import flash.events.Event;
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
import flash.events.TouchEvent;
public class MouseClient extends Sprite {
// Сокеты для работы с UDP
private var socket : DatagramSocket;
// Массив байт, которые будем отправлять
private var bytes_move : ByteArray = new ByteArray();
private var bytes_left : ByteArray = new ByteArray();
private var bytes_right : ByteArray = new ByteArray();
// Объект, который хранит данные отправляемые на сервер
private var mouseMoving : Object = {id:0,x:0,y:0};
private var mouseLeftUpDown : Object = {id:1,isdown:false};
private var mouseRightUpDown : Object = {id:2,isdown:false};
// ip сервера и порт
private var server : Object = {ip:0,port:0};
// Храним точку нажатия и дельту смещения
private var downx : int = 0;
private var downy : int = 0;
private var delatax : int = 0;
private var delatay : int = 0;
// Нажат ли pad
private var isDown : Boolean = false;
public function MouseClient() {
// Указываем, что ввод будет считывать сразу много нажатий. Это необходимо для одновременной отработки move и up/down
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
addEventListener(Event.ADDED_TO_STAGE, onAdded);
}
private function onAdded(e:Event):void{
removeEventListener(Event.ADDED_TO_STAGE, onAdded);
// Скрываем пока что кнопки управления
buttonLeft.visible = false;
buttonRight.visible = false;
// Привязываем кнопку подключения
ConnectButton.addEventListener(MouseEvent.CLICK, tryConnect);
}
private function tryConnect(e:MouseEvent):void{
ConnectButton.removeEventListener(MouseEvent.CLICK, tryConnect);
// Меняем видимость элементов UI
IP.visible = false;
PORT.visible = false;
ConnectButton.visible = false;
buttonLeft.visible = true;
buttonRight.visible = true;
// После нажатия на подключение - создаем UDP сокет подключение
socket = new DatagramSocket();
// сохраняем в объект ip сервера и port
server.ip = IP.text;
server.port = PORT.text;
// Вешаем слушатели на элементы UI
// Важно иметь именно мультитачевые, т.к. у нас могут быть и нажатия кнопок и смещение курсора
pad.addEventListener(TouchEvent.TOUCH_BEGIN, doMouseDown);
pad.addEventListener(TouchEvent.TOUCH_MOVE, doMouseMove);
pad.addEventListener(TouchEvent.TOUCH_END, doMouseUp);
buttonLeft.addEventListener(TouchEvent.TOUCH_BEGIN, lb);
buttonLeft.addEventListener(TouchEvent.TOUCH_END, lb);
buttonRight.addEventListener(TouchEvent.TOUCH_BEGIN, rb);
buttonRight.addEventListener(TouchEvent.TOUCH_END, rb);
}
private function lb(e:TouchEvent):void{
if ( e.type == TouchEvent.TOUCH_BEGIN ) {
mouseLeftUpDown.isdown = true;
} else {
mouseLeftUpDown.isdown = false;
}
bytes_left.length = 0;
bytes_left.writeObject(mouseLeftUpDown);
bytes_left.compress();
socket.send( bytes_left,0,0,server.ip, server.port);
}
private function rb(e:TouchEvent):void{
if ( e.type == TouchEvent.TOUCH_BEGIN ) {
mouseRightUpDown.isdown = true;
} else {
mouseRightUpDown.isdown = false;
}
bytes_right.length = 0;
bytes_right.writeObject(mouseRightUpDown);
bytes_right.compress();
socket.send( bytes_right,0,0,server.ip, server.port);
}
private function doMouseUp(e:TouchEvent):void{
isDown = false;
}
private function doMouseDown(e:TouchEvent):void{
downx = e.stageX;
downy = e.stageY;
isDown = true;
}
private function doMouseMove(e:TouchEvent):void{
if ( isDown ){
// получаем дельту смещения, чтоб понять в какую сторону двигать курсор
delatax = downx - e.stageX;
delatay = downy - e.stageY;
mouseMoving.x = -delatax;
mouseMoving.y = -delatay;
downx = e.stageX;
downy = e.stageY;
// стираем длину bytearray
bytes_move.length = 0;
// пишем объект с координатами x/y
bytes_move.writeObject(mouseMoving);
// для экономии трафиика - ужимаем bytearray
bytes_move.compress();
// отправляем сформированый bytearray на сервер в порт
socket.send( bytes_move,0,0,server.ip, server.port);
}
}
}
}
Теперь создадим Adobe Air проект для Windows и прилинкуем туда наш ANE, который мы создали выше.
package {
import flash.display.Sprite;
import flash.net.DatagramSocket;
import flash.events.DatagramSocketDataEvent;
import flash.events.Event;
import flash.desktop.NativeApplication;
import flash.utils.ByteArray;
import com.therabbit.utils.pc.MousePosition;
public class Server extends Sprite {
private var dataserver : DatagramSocket;
private var inputData : ByteArray = new ByteArray();
public var receivedData : Object;
public function Server() {
dataserver = new DatagramSocket();
dataserver.addEventListener(DatagramSocketDataEvent.DATA, onClientConnect);
dataserver.bind(7890);
dataserver.receive();
NativeApplication.nativeApplication.addEventListener(Event.EXITING, app_exit);
}
private function app_exit(e:Event):void{
dataserver.close();
MousePosition.dispose();
}
private function onClientConnect(e:DatagramSocketDataEvent):void{
e.data.readBytes(inputData);
inputData.uncompress();
receivedData = inputData.readObject();
inputData.length = 0;
if ( receivedData.id == 0 ) {
MousePosition.setMousePosition(receivedData.x,receivedData.y);
}
if ( receivedData.id == 1 ) {
MousePosition.leftButton(receivedData.isdown);
}
if ( receivedData.id == 2 ) {
MousePosition.rightButton(receivedData.isdown);
}
}
}
}
Теперь в 2х словах как это будет работать. Наш компьютер-сервер будет запускать приложение Server и будет слушать текущий компьютер на предмет входящих UDP пакетов. В строке dataserver.bind(7890); мы слушаем ip 0.0.0.0 что значит «слушать все адреса данного компьютера». И 7890 — это порт, который я ввел произвольно.
Теперь самое главное — отключить firewall компьютера или разрешить для нашего Server.exe порт 7890. Для теста я просто отключил firewall. Пакеты приходят в компьютер через WiFi.
Запускаем iOS клиента, вводим IP нашего компьютера и вводим порт (по умолчанию введен). Для тех, кто не знает как узнать текущий IP — это можно сделать с помощью того же Adobe Air. Но для более быстрого завершения статьи — узнаем через коммандную строку с помощью ipconfig :)

Запускаем скорее на iOS продукт. Запускаем наш сервер. На iOS нажимаем Connect и все :) Водим пальцем на iPad и у нас двигается курсор на компьютере. Можем даже нажимать на виртуальные кнопки на экране и они чудесно уходят в комп :) Можно в фотошопе порисовать даже :)
Кому уже нетерпится поиграться без компиляций и шаманства — www.dropbox.com/s/a8g8gf9ojmk1s5c/Win7MouseControler.zip
В архиве мануал и файлы для запуска. Статья хоть и большая — уверяю, в создании Native Extensions нет совершенно ничего сложного!
- +23
- TheRabbit
Комментарии (27)
Аль совсем башкой ослаб?
Где бы что ни говорили —
Все одно сведет на баб!»
© Леонид Филатов
www.geek.com/apple/the-5-best-remote-desktop-apps-for-ipad-1565111
Есть бесплатные, есть платные. Думаю, что возможности там пошире и поддержка есть.
Какой смысл был свое писать?
Надо было иметь минимальный набор функций плюс брендирование экрана. Это возможно только в своем продукте. Вам это не понять всем, т.к. Вы никогда не работали в маркетинговом агентстве. Это становится ясно исходя из вопросов :)
Да и смотрите шире. Статья тут появилась не для поиска аналога, а для демонстрации написания ANE. Иначе не писал бы вовсе. На форуме человек спрашивал как из Air получить WMI информацию. Если он прочитает статью — он напишет на базе этого примера свой.
P.S. зачем вы делаете игры, если можно работать грузчиком, а пользователи могут играть в другие, которые уже написаны до Вас?
Да-да, не стесняйся, говори уж, что про всех тут думаешь…
значит — это кому-нибудь нужно?»
© Владимир Маяковский
Человек сделал и показал пример реального использования ANE, думаю стоит рассматривать именно в таком контексте.
Критика выше необоснованна. Я могу нагенерировать два десятка идей программ, которые можно создать на этом движке.
Например: файлообменник, менеджер паролей iOS хранящийся на PC, трейсер отладочной инфы (упрощенный Scout) ну и т. д.
Туторов «Hello world» больше, чем атомов в солнце, но никто не указывает авторам новых на то, что «На других языках их уже полно».
Больше постов хороших и разных на блоги, не отбивайте мотивацию!
Статьи – это хорошо, новые знания – отлично, хэлловорлды – превосходно. Вопрос то был в том, зачем же маркетинговому агентству понадобилось заказывать «Hello world». Зачем это нужно Кролику, семи друзьям Абисса и выдумщику Астрапорту – вполне понятно.
В разработку поступил проект с конкретными требования — они были выполнены. Меньше вопросов — больше результата. Ты еще спроси зачем маркетинговые агентства делают акционные игры, если можно скачать готовые.
Зачем вообще в магазин ходить за хлебом, если его можно дома делать. Зачем муку покупать, если можно пшеницу молоть дома… Что-то у вас, батенька, представления о маркетинговых агентствах хромают. Ты еще скажи, что считывания данных акселерометра с девайса и отправка их на комп, где в usb включено устройство и это все тоже через Air работает — тоже hello world, который маркетинговому агенству не надо… Боюсь даже представь в каком веке ты живешь. В прошлом или позапрошлом, где весь маркетинг — это реклама на заборе главпочтамта.
Твоё дело прочитать молча. Не понравилось — прошел мимо. Понравилось — оставил каммент. Но задавать вопросы «зачем» — оставь пожалуйста жене. Каждый день спрашивай «зачем ты готовишь борщь. Мы же можем питаться в макдональдсе».
Меня на самом деле не волнует кто и что заказывал, но непонятен факт неприятия технической статьи на блоге, хотя их итак последнее время практически нет.
Наверное что-то личное.
Про неприятие это больше относилось как раз к комментарию Andreww.
Зачем спрашивать почему человек сделал свое решение на флеше на флешблогах?
Потому что хотел показать, разобраться сам или Because I can, наконец.
TheRabbit объяснил потом свое решение.
У него своя специфика, клиенту понадобилось брендированное решение или он придумал новую фишку, и что дальше? Обращаться к авторам софта для внесения правок и доработок?
Так в чем состоит комментарий по содержанию? Почему человек делает свое решение для разбора технологии вместо того, чтобы оставить ссылку на видео установки решения не на флеше?
Если честно, не увидел в процитированном высказывании ни:
ни факта:
ни вопроса:
И мне показалось, или последнее высказывание немного сквозит флеш-шовинизмом: «Флешблоги – для флешеров»?
На всякий случай профилактическая пилюля от флазма:
— подписываюсь под сказанным.
Вопрос был о целесообразности, это же немаловажный вопрос в принятии решений. А решаемые задачи были обозначены в статье, отсюда «по содержанию»
Не понял причем тут цитата флазма, с которой я согласен, но не надо подставлять правильные цитаты, пытаясь все остальное тоже выдать за истину.
Обвинение в шовинизме? Я где-то призывал игнорировать другие технологии или использовать только флеш?
Цель была показать написание расширения, а претензии почему в целесообразности написания. Впрочем про целесообразность уже ответили несколько раз, включая примечание про брендированный экран.
Я все-таки еще раз это сделаю, просто хотел бы увидеть цитаты подтверждающие:
Вопрос — задан, ответ — получен, не понимаю, какие претензии к вопросу Andreww, причем здесь неприятие технической статьи?
Порой я тебя не узнаю. Был интересный собеседник, узнал от тебя много чего полезного. Но потом ты стал писать как пенсионер, которому надо набить 100500 любых сообщений или просто словосочетаний, чтоб получить ачивку.
Смысла в твоих словах не уловил не только я, но еще и кучу людей, которые это сейчас обсасывают вне блогов и форумов. И все согласны с тем, что ты тролль. А чтоб так жирно троллить — ты должен в день по 2-3 статьи писать, чтоб общество не чувствовало потребность в новых постах. Которых и так все меньше и меньше. Благодаря как раз таким действиям, которые ты проводишь. Прям троянский конь какой-то.
Ты мою статью называл Hello World. Окай. Покажи свою не hello world?
P.S. ты владелец маркетиногово агенства? Ты лучше других знаешь как надо и для чего? Я в этом очень сомневаюсь.
Система наведения для запуска пенопластовых шариков тоже сделалась мной на iOS+PC Air + Arduino. Зачем это маркетинговому агенству? Затем, чтоб это не заказывать на стороне, где будет отсутствовать поддержка и все прелести, когда «не сам» делаешь.
Ты просто пытаешься обидеть людей тем, что якобы ты лучше их знаешь, как это лучше сделать для них же. Ну раз это так — почему не ты это сделал? Почему большой проект, где моя реализация управления мышкой это лишь один из пунктов от общего числа задач по рекламной кампании сделана была нашим предприятием, а не тобой лично?
Если у тебя есть не приязнь ко мне — живи с ней, это твои личные мироощущения. Мяса в морозилке у меня меньше не станет из-за этого. А к Zakrua, в целом, вопросов нет — мы с ним нормально общаемся и микро-троллинг допустим.
Я пишу акционные статьи для пенсионного фонда. Прости — NDA.