Управление мышью ПК посредством мобильного на 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 нет совершенно ничего сложного!

Комментарии (27)

+3
На работе клиенту понадобилось двигать мышку на его PC под управлением Windows7 удаленно с помощью iPad.
Microsoft Remote Desktop?
+8
но он не на Air.
+2
«Постеснялся хоть посла б!
Аль совсем башкой ослаб?
Где бы что ни говорили —
Все одно сведет на баб!»

© Леонид Филатов
+3
Я тоже не совсем уловил, есть же официальный remote desctop от майкрософт, есть еще сколько-то решений —
www.geek.com/apple/the-5-best-remote-desktop-apps-for-ipad-1565111

Есть бесплатные, есть платные. Думаю, что возможности там пошире и поддержка есть.
Какой смысл был свое писать?
+8
но он не на Air.
Ты становишься скучен :) Похоже Air волнует тебя больше, чем меня.
Какой смысл был свое писать?
Надо было иметь минимальный набор функций плюс брендирование экрана. Это возможно только в своем продукте. Вам это не понять всем, т.к. Вы никогда не работали в маркетинговом агентстве. Это становится ясно исходя из вопросов :)
Да и смотрите шире. Статья тут появилась не для поиска аналога, а для демонстрации написания ANE. Иначе не писал бы вовсе. На форуме человек спрашивал как из Air получить WMI информацию. Если он прочитает статью — он напишет на базе этого примера свой.

P.S. зачем вы делаете игры, если можно работать грузчиком, а пользователи могут играть в другие, которые уже написаны до Вас?
+2
> Вам это не понять всем
Да-да, не стесняйся, говори уж, что про всех тут думаешь…
+4
Нормально я все думаю про всех :) Если бы весь существующий софт удовлетворял бы все потребности человечества — новый и не появлялся бы вовсе ;)
0
Зачем вы делаете игры, пользователи могут играть в другие, которые уже написаны до Вас?
Зачем понадобился кастрированный клон сапера на стенсиле — вот в чём вопрос :D
0
«Ведь, если звезды зажигают —
значит — это кому-нибудь нужно?»

© Владимир Маяковский
+9
Ну как бы в данном случае претензии мимо кассы.
Человек сделал и показал пример реального использования ANE, думаю стоит рассматривать именно в таком контексте.
+1
Спасибо большое.
Критика выше необоснованна. Я могу нагенерировать два десятка идей программ, которые можно создать на этом движке.
Например: файлообменник, менеджер паролей iOS хранящийся на PC, трейсер отладочной инфы (упрощенный Scout) ну и т. д.
+5
Товарищи, плохой пост?
Туторов «Hello world» больше, чем атомов в солнце, но никто не указывает авторам новых на то, что «На других языках их уже полно».
Больше постов хороших и разных на блоги, не отбивайте мотивацию!
+3
Товарищи, плохой пост?
С чего ты это взял?

Статьи – это хорошо, новые знания – отлично, хэлловорлды – превосходно. Вопрос то был в том, зачем же маркетинговому агентству понадобилось заказывать «Hello world». Зачем это нужно Кролику, семи друзьям Абисса и выдумщику Астрапорту – вполне понятно.
+4
зачем же маркетинговому агентству понадобилось заказывать «Hello world»
Ох ты ж епт… что же интересно не «hello world» в твоем понимании? Управление потенцией через айпад?
В разработку поступил проект с конкретными требования — они были выполнены. Меньше вопросов — больше результата. Ты еще спроси зачем маркетинговые агентства делают акционные игры, если можно скачать готовые.

Зачем вообще в магазин ходить за хлебом, если его можно дома делать. Зачем муку покупать, если можно пшеницу молоть дома… Что-то у вас, батенька, представления о маркетинговых агентствах хромают. Ты еще скажи, что считывания данных акселерометра с девайса и отправка их на комп, где в usb включено устройство и это все тоже через Air работает — тоже hello world, который маркетинговому агенству не надо… Боюсь даже представь в каком веке ты живешь. В прошлом или позапрошлом, где весь маркетинг — это реклама на заборе главпочтамта.

Твоё дело прочитать молча. Не понравилось — прошел мимо. Понравилось — оставил каммент. Но задавать вопросы «зачем» — оставь пожалуйста жене. Каждый день спрашивай «зачем ты готовишь борщь. Мы же можем питаться в макдональдсе».
0
Не понял наезда, какие друзья имелись виду?

Меня на самом деле не волнует кто и что заказывал, но непонятен факт неприятия технической статьи на блоге, хотя их итак последнее время практически нет.

Наверное что-то личное.
+2
Не понял наезда? Может, потому что наезда там и нет вовсе? Просто обобщил тебя и солидарных с твоим комментарием, отсылкой к одиннадцати друзьям Оушена. В том высказывании нет никакого двойного дна — просто констатация происходящего. Может, просветишь о факте неприятия технической статьи? Пока только видно раздувание проблемы популистскими комментариями. Единственный комментарий по содержанию статьи, как ни странно, за авторством Andreww, и там, по моему мнению, прозвучал вполне резонный вопрос.
+2
Тогда извиняюсь, но отсылка также неуместна, если кто-то согласен с конкретной фразой, то он совсем не обязательно являются «другом Оушена».

Про неприятие это больше относилось как раз к комментарию Andreww.
Есть бесплатные, есть платные. Думаю, что возможности там пошире и поддержка есть.
Какой смысл был свое писать?
Зачем спрашивать почему человек сделал свое решение на флеше на флешблогах?

Потому что хотел показать, разобраться сам или Because I can, наконец.
TheRabbit объяснил потом свое решение.
У него своя специфика, клиенту понадобилось брендированное решение или он придумал новую фишку, и что дальше? Обращаться к авторам софта для внесения правок и доработок?

Так в чем состоит комментарий по содержанию? Почему человек делает свое решение для разбора технологии вместо того, чтобы оставить ссылку на видео установки решения не на флеше?
+2
Друзья Оушена не являются же друзьями по факту, за некоторым исключением, они просто сообщники, а если отбросить деятельность – единомышленники, в этом контексте и использовал.

Если честно, не увидел в процитированном высказывании ни:
плохой пост
ни факта:
неприятия технической статьи на блоге
ни вопроса:
почему решение на флеше на флешблогах
И мне показалось, или последнее высказывание немного сквозит флеш-шовинизмом: «Флешблоги – для флешеров»?
На всякий случай профилактическая пилюля от флазма:
Многие сейчас разрабатывают одновременно на разных платформах, в текущих условиях это нормально. Но я по-прежнему настаиваю на том, что нас как сообщество объединяют игры, а платформы — это дело второе.
— подписываюсь под сказанным.

Так в чем состоит комментарий по содержанию?
Вопрос был о целесообразности, это же немаловажный вопрос в принятии решений. А решаемые задачи были обозначены в статье, отсюда «по содержанию»
На работе клиенту понадобилось двигать мышку на его PC под управлением Windows7 удаленно с помощью iPad.
+2
Вопрос был о целесообразности, это же немаловажный вопрос в принятии решений. А решаемые задачи были обозначены в статье, отсюда «по содержанию»
Еще раз повторяю — задача была выполнена и в срок. За нее клиент оплатил предприятию и все довольны. Мы современные люди и естественно знаем с десяток разных remote desktop решений. И у всех нет того, что нам надо — брендирование экрана самого приложения.
0
Сообщники подразумевают сговор, тут его не было.

Не понял причем тут цитата флазма, с которой я согласен, но не надо подставлять правильные цитаты, пытаясь все остальное тоже выдать за истину.
Обвинение в шовинизме? Я где-то призывал игнорировать другие технологии или использовать только флеш?

Цель была показать написание расширения, а претензии почему в целесообразности написания. Впрочем про целесообразность уже ответили несколько раз, включая примечание про брендированный экран.
+2
Сообщники подразумевают сговор
Не подразумевает.
Сообщник — единомышленник в каком-либо деле

не надо подставлять правильные цитаты

Я все-таки еще раз это сделаю, просто хотел бы увидеть цитаты подтверждающие:
факт неприятия технической статьи на блоге
вопрос почему решение на флеше на флешблогах


Впрочем про целесообразность уже ответили
Вопрос — задан, ответ — получен, не понимаю, какие претензии к вопросу Andreww, причем здесь неприятие технической статьи?
+4
Claymore, объясни мне тупому, что вообще это значит и каким оно боком относится к теме? Какие друзья, какие Оушены? Меньше смотри телевизор.
Зачем это нужно Кролику, семи друзьям Абисса и выдумщику Астрапорту
Просто обобщил тебя и солидарных с твоим комментарием, отсылкой к одиннадцати друзьям Оушена.
Зачем понадобился кастрированный клон сапера на стенсиле — вот в чём вопрос :D

Порой я тебя не узнаю. Был интересный собеседник, узнал от тебя много чего полезного. Но потом ты стал писать как пенсионер, которому надо набить 100500 любых сообщений или просто словосочетаний, чтоб получить ачивку.

Смысла в твоих словах не уловил не только я, но еще и кучу людей, которые это сейчас обсасывают вне блогов и форумов. И все согласны с тем, что ты тролль. А чтоб так жирно троллить — ты должен в день по 2-3 статьи писать, чтоб общество не чувствовало потребность в новых постах. Которых и так все меньше и меньше. Благодаря как раз таким действиям, которые ты проводишь. Прям троянский конь какой-то.

Я все-таки еще раз это сделаю, просто хотел бы увидеть цитаты подтверждающие:
Вопрос то был в том, зачем же маркетинговому агентству понадобилось заказывать «Hello world».
Ты мою статью называл Hello World. Окай. Покажи свою не hello world?

P.S. ты владелец маркетиногово агенства? Ты лучше других знаешь как надо и для чего? Я в этом очень сомневаюсь.

Система наведения для запуска пенопластовых шариков тоже сделалась мной на iOS+PC Air + Arduino. Зачем это маркетинговому агенству? Затем, чтоб это не заказывать на стороне, где будет отсутствовать поддержка и все прелести, когда «не сам» делаешь.

Ты просто пытаешься обидеть людей тем, что якобы ты лучше их знаешь, как это лучше сделать для них же. Ну раз это так — почему не ты это сделал? Почему большой проект, где моя реализация управления мышкой это лишь один из пунктов от общего числа задач по рекламной кампании сделана была нашим предприятием, а не тобой лично?

Если у тебя есть не приязнь ко мне — живи с ней, это твои личные мироощущения. Мяса в морозилке у меня меньше не станет из-за этого. А к Zakrua, в целом, вопросов нет — мы с ним нормально общаемся и микро-троллинг допустим.
+3
микро-троллинг допустим.
спасибо, клеймора я сам ненавижу, просто он статей не пишет и шанса сказать ему нет.
+2
Та его все ненавидят (кто ж этих умников любит-то) просто из-за врожденной интеллигентности не могут высказаться.
+1
Сам себя ненавижу. Хотел заняться самобичеванием в статье — но не пишу статей, увы. Всему виной приобретенная неотесанность :D
+2
Ты мою статью называл Hello World
Не выдумывай! Я не называл так статью. Выражение взято в кавычки. Выражение – смысловая привязка к предыдущему комментарию MidnightOne, и цитат из него же.

Покажи свою не hello world?
Я пишу акционные статьи для пенсионного фонда. Прости — NDA.
0
Я пишу акционные статьи для пенсионного фонда. Прости — NDA.
А, ну в целом да. Обычно пишут про свою жизнь текущую :)
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.