HOME
  Security
   Software
    Hardware
  
FPGA
  CPU
   Android
    Raspberry Pi
  
nLite
  Xcode
   etc.
    ALL
  
LINK
BACK
 

2016/12/01

Xcodeで Mac OSX用のクリップボード共有&履歴アプリを作る Xcodeで Mac OSX用のクリップボード共有&履歴アプリを作る

(Mac OS X用アプリを Swift言語で作る方法、MacOSXでクリップボード監視で変化が有ったら取得)

Tags: [Apple], [MacBook]




● Xcode 7.3.1 Swift 2.2.1で OS X用のアプリを作る

 Swiftは Xcode 8の Swift 3になって ++演算子が使えないとか、システムの定数定義のスペルが変わっていたりと大変です。Swift死ねって感じ。

Welcome to Swift.org

 ここでは、Xcode 7.3.1 Swift 2.2.1を使用してアプリを作ります。

 今まで Windowsで使っていた俺アプリの
 ・クリップボード履歴アプリの ClipHistW
 ・クリップボード共有アプリの ClipLanW
 を Mac OS X El Capitan 10.11.6に移植します。

●クリップボードの内容を履歴保存する部分

 Macの場合はクリップボード オブザーバーの仕組みが無いのでタイマーを使って一定間隔で監視して違っていたら保存と言う実装が慣例となっています。
 今回は、タイマーの使い方がまだ解らないのでキーボードフックを行い、Ctrl+Cや Ctrl+Xのキー操作の時にクリップボードからテキスト情報を取り出して保存する様にします。

 主な実装処理
 ・キーボードのグローバルフックでクリップボードの履歴保存
 ・キーボードのグローバルフックでクリップボードの履歴変更処理
 ・クリップボードからテキストデータの取得
 ・クリップボードへテキストデータの設定
 ・クリップボードテキスト処理時の通知ウィンドウの表示
 ・UDP受信のスレッド処理
 ・UDP受信でクリップボード共有テキスト情報の受信
 ・ステータスバーに常駐化
 ・メインウィンドウにクリップボード履歴の一覧を表示
 ・その他、共有通信の設定画面とか

 実装として残りの部分は
 ・UDP通信の受信処理をスレッド化 Macだと Grand Central Dispatch(GCD)化(実装済み)
 ・ステータスバーに常駐化(実装済み)
 ・メインウィンドウにクリップボード履歴の一覧を表示
 ・その他、共有通信の設定画面とか
 となります。


●キーボードフック Mac OS X + Xcode 7.3.1 Swift 2.2.1
●キーボード操作のグローバルフック Mac OS X + Xcode 7.3.1 Swift 2.2.1
● UDP送信処理 Mac OS X + Xcode 7.3.1 Swift 2.2.1
● UDP受信処理 Mac OS X + Xcode 7.3.1 Swift 2.2.1
● UDP受信のスレッド処理 Mac OS X + Xcode 7.3.1 Swift 2.2.1
●クリップボードからテキスト情報の取得 Mac OS X + Xcode 7.3.1 Swift 2.2.1
●クリップボードへテキスト情報の設定 Mac OS X + Xcode 7.3.1 Swift 2.2.1
●通知ウィンドウの表示 Mac OS X + Xcode 7.3.1 Swift 2.2.1
●ステータスバーに常駐化 Mac OS X + Xcode 7.3.1 Swift 2.2.1

//  Copyright (c) 2016 FREE WING,Y.Sakamoto. All rights reserved.

    // スレッド処理の同期用
    let semaphore:dispatch_semaphore_t = dispatch_semaphore_create(0)
    let queue:dispatch_queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)


    // キーボードフックを設定する所
    func applicationDidFinishLaunching(aNotification: NSNotification) {
        // Insert code here to initialize your application

        NSLog("** applicationDidFinishLaunching")

        // 信頼するアプリチェック
        let options = NSDictionary(object: kCFBooleanTrue, forKey: kAXTrustedCheckOptionPrompt.takeUnretainedValue() as NSString) as CFDictionaryRef
        let trusted = AXIsProcessTrustedWithOptions(options)

        // 信頼するアプリチェック
        if (trusted) {
            // キーボードを押した時のフックを設定
            NSEvent.addGlobalMonitorForEventsMatchingMask(.KeyDownMask, handler: self.handlerKeyDownMask)
            // キーボードを離した時のフックを設定
            NSEvent.addGlobalMonitorForEventsMatchingMask(.KeyUpMask, handler: self.handlerKeyUpMask)
        }

      // UDP通信の受信処理をスレッド化 Macだと Grand Central Dispatch(GCD)化
      dispatch_async(queue) {() -> Void in
          // 受信処理
          self.receiveUdp()

          dispatch_semaphore_signal(semaphore)
      }

        // ステータスバー常駐設定
        initStatusBarMenu()
    }


    // プログラム終了時
    func applicationWillTerminate(aNotification: NSNotification) {
        // Insert code here to tear down your application
        // receiveUdp()の終了指示
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
    }


    // キーボードを押した時に呼ばれるコールバック関数
    func handlerKeyUpMask(event: NSEvent!) {

        print(event)

        if (!event.modifierFlags.contains([.CommandKeyMask])) {
            return
        }

        // Ctrl+X
        if event.keyCode == 7 {
            // クリップボードの中身を保存する処理
        }

        // Ctrl+C
        if event.keyCode == 8 {
            // クリップボードの中身を保存する処理
        }
    }

    // キーボードを離した時に呼ばれるコールバック関数
    func handlerKeyDownMask(event: NSEvent!) {

        print(event)

        // CommandKey + AlternateKey + ShiftKey
        if (!event.modifierFlags.contains([.CommandKeyMask, .AlternateKeyMask, .ShiftKeyMask])) {
            return
        }

        // CommandKey + AlternateKey + ShiftKey + '['
        // '['
        if event.keyCode == 30 {
            // クリップボードの履歴ポインタを -1する処理
        }

        // CommandKey + AlternateKey + ShiftKey + ']'
        // ']'
        if event.keyCode == 42 {
            // クリップボードの履歴ポインタを +1する処理
        }
    }


    // クリップボードからテキストを取り出す処理
    func getStringFromPasteboard() -> (Bool, String?) {

        let board = NSPasteboard.generalPasteboard()

        // クリップボードが空
        if board.pasteboardItems?.count == 0 {
            return (false, nil);
        }

        // クリップボードの中身がテキストの時に取得保存
        var strArr: Array<String> = []
        for item in board.pasteboardItems! {
            if let str = item.stringForType("public.utf8-plain-text") {
                strArr.append(str)
                NSLog(str)
            }
        }

        // テキスト情報が無い
        if strArr.count == 0 {
            return (false, nil);
        }

        // クリップボードのテキストを返す
        return (true, strArr[0]);
    }


    // クリップボードにテキストを設定する処理
    func setStringToPasteboard(string: String) {

        // クリップボードを空にしてから中身を設定する
        let board = NSPasteboard.generalPasteboard()
        board.clearContents()

        let item = NSPasteboardItem()
        item.setString(string, forType: NSPasteboardTypeString)
        board.writeObjects([item])
    }


    // ClipHistWのピョコウィンドウの代わり
    // 通知を表示するサンプル
    {
        NSUserNotificationCenter.defaultUserNotificationCenter().delegate = self

        let notification = NSUserNotification()
        notification.title = "タイトル"
        notification.subtitle = "サブタイトル"
        notification.informativeText = "テキスト本文"
        notification.contentImage =  NSImage(named: "blue")
        notification.userInfo = ["title" : "タイトル"]
        NSUserNotificationCenter.defaultUserNotificationCenter().deliverNotification(notification)
    }


    // UDP送信のサンプル
    func sendUdp() {

        let sock: Int32 = socket(AF_INET, SOCK_DGRAM, 0)

        var addr = sockaddr_in()
        addr.sin_family = sa_family_t(AF_INET)
        addr.sin_port = UInt16(12345).bigEndian
        addr.sin_addr = in_addr()
        addr.sin_addr.s_addr = inet_addr("192.168.1.123")

        var data: [UInt8] = [0x41, 0x42, 0x43, 0x55, 0x38]
        var sendSize : Int = data.count

        let r = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sendSize, socklen_t(sizeofValue(Int)))

        if (r == 0) {
            // Err
            return
        }

        withUnsafePointer(&addr) { ptr -> Void in
            let addrPtr = UnsafePointer<sockaddr>(ptr)
            sendto(sock, &data, Int(data.count), 0, addrPtr, socklen_t(sizeofValue(addr)))
        }

        close(sock)
    }


    // UDP受信のサンプル
    func receiveUdp() {

        let sock: Int32 = socket(AF_INET, SOCK_DGRAM, 0)

        var addr = sockaddr_in()
        addr.sin_family = sa_family_t(AF_INET)
        addr.sin_port = UInt16(12345).bigEndian
        addr.sin_addr = in_addr()

        let status = bindresvport(sock, &addr)
        if (status < 0) {
            let errMsg = String(UTF8String: strerror(errno))
            print(errMsg)

            return
        }

        // Loop
        while (true) {

            var buffer: [UInt8] = [UInt8](count: 32768, repeatedValue: 0)
            let cliaddr = UnsafeMutablePointer<sockaddr_in>.alloc( sizeof(sockaddr_in))

            var rlen = socklen_t(sizeof(sockaddr_in))
            let n = recvfrom(sock, &buffer, Int(buffer.count), 0, UnsafeMutablePointer<sockaddr>(cliaddr) , &rlen)

            if (n < 0) {
                let errMsg = String(UTF8String: strerror(errno))
                print(errMsg)

                return
            }

            // ClipLanWのパケット構造
            var xorKey = buffer[0]
            let hiByte = buffer[1]
            let loByte = buffer[2]
 //          let format = buffer[3]

            let byteSize: Int = ((numericCast(hiByte) << 8) + numericCast(loByte))

            var ptr = UnsafePointer<UInt8>(buffer)
            ptr = ptr.advancedBy(4)

            // 通信データを復号化する
            for i: Int in 0..<byteSize {
                let cryptData = buffer[(i+4)]
                buffer[i+4] = xorKey ^ cryptData
                xorKey = cryptData
            }

            // バイナリデータを UTF-16の文字列に変換する
            let str = String.init(utf16CodeUnits: UnsafePointer<unichar>(ptr), count: (byteSize/2-1))

            // 受信したデータをクリップボードに設定する
            setStringToPasteboard(str)
        }

        close(sock)
    }

●ステータスバーに常駐化 Mac OS X + Xcode 7.3.1 Swift 2.2.1

 /*
Info.plistに下記を設定する(Dockに表示しない。常駐アプリモード・バックグラウンド・アプリケーションモード)
Application is agent (UIElement) = YES

<key>LSUIElement</key>
<string>YES</string>
 */

    var statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(NSVariableStatusItemLength)

    // ステータスバー常駐設定
    func initStatusBarMenu() {

        let menu = NSMenu()
        // ステータスバーに文字列を表示する場合
        //  self.statusItem.title = "FREE WING"
        // ステータスバーにアイコン画像を表示する場合(x1画像で 16 x 16px)
        self.statusItem.image = NSImage(named: "StatusBarIcon");
        self.statusItem.highlightMode = true
        self.statusItem.menu = menu

        let menuItem = NSMenuItem()
        menuItem.title = "Exit"
        menuItem.action = #selector(AppDelegate.exitApplication(_:))
        menu.addItem(menuItem)
    }

    @IBAction func exitApplication(sender : NSMenuItem) {
        // Insert code here to tear down your application
        // プログラム終了
        NSApplication.sharedApplication().terminate(self)
    }




Tags: [Apple], [MacBook]

●関連するコンテンツ(この記事を読んだ人は、次の記事も読んでいます)

Xcode SWIFTで Mac OSX用のスクリーンマスコットアプリを作る
Xcode SWIFTで Mac OSX用のスクリーンマスコットアプリを作る

  Mac OS X用アプリを Swift言語で作る方法、デスクトップ画面に画像をオーバーレイで描画する方法

Xcode SWIFTで Mac OSX用のスクリーンキャプチャアプリ&画像保存を作る
Xcode SWIFTで Mac OSX用のスクリーンキャプチャアプリ&画像保存を作る

  Mac OS X用アプリを Swift言語で作る方法、デスクトップの画像をキャプチャする方法

Xcode SWIFTで Mac OSX用のスクリーン拡大鏡アプリを作る
Xcode SWIFTで Mac OSX用のスクリーン拡大鏡アプリを作る

  Mac OS X用アプリを Swift言語で作る方法、マウスカーソルの位置をリアルタイムで取得する方法

Xcode SWIFTで Mac OSX用の Finderからドラッグ&ドロップを受け付けるアプリを作る
Xcode SWIFTで Mac OSX用の Finderからドラッグ&ドロップを受け付けるアプリを作る

  Mac OS X用アプリを Swift言語で作る方法、ドラッグ&ドロップを受け付ける方法

Xcodeで Mac OSX用の使えそうな処理を Tipsとしてまとめ
Xcodeで Mac OSX用の使えそうな処理を Tipsとしてまとめ

  Mac OS X用アプリを Swift言語で作る方法

Apple MacBook Airを買ってみた。開発者必須アプリの紹介
Apple MacBook Airを買ってみた。開発者必須アプリの紹介

  Xcodeで iPhoneの開発の為に、Mac OS X用アプリを Swift言語で作る方法

Windowsパソコンで Macる。Skylake Platform in OSX86 Hackintoshの夢、BIOS設定内容
Windowsパソコンで Macる。Skylake Platform in OSX86 Hackintoshの夢、BIOS設定内容

  Windows PCで iPhone開発、Mac OS Xで Hackintoshの方法、DVMT Pre-Allocated 128MB

VirtualBoxに Mac OS X macOS Sierraをインストールの夢
VirtualBoxに Mac OS X macOS Sierraをインストールの夢

  Windowsの VirtualBoxに Mac環境を作り OS Xをインストールして動かす夢を見る

Raspberry Pi3で iPhoneの開発言語でお馴染みの Swiftを動かし、GPIOを操作して Lチカする方法
Raspberry Pi3で iPhoneの開発言語でお馴染みの Swiftを動かし、GPIOを操作して Lチカする方法

  Raspbian Jessieで Swift言語のセットアップ方法、GPIOの LEDを Lチカや I2Cを制御する方法




[HOME] | [BACK]
リンクフリー(連絡不要、ただしトップページ以外は Web構成の変更で移動する場合があります)
Copyright (c) 2016 FREE WING,Y.Sakamoto
Powered by 猫屋敷工房 & HTML Generator

http://www.neko.ne.jp/~freewing/xcode/xcode_swift_1_clipboard/