#!/bin/bash

#
# vim:shiftwidth=2:tabstop=2:expandtab:textwidth=80:softtabstop=4:ai
#
# This script generates udev and/or policykit rules for Logitech Harmony
# devices.
#
# This script is loosely-based on scripts by Douglas E. Warner
# <silfreed@silfreed.net>
#
# GPLv2
#
# Copyright 2009 Phil Dibowitz
#

# CONDITIONS
# varios "conditions" to make udev rules

# A partial template, used largely when generating one-line per device
UDEV_CONDITION_PARTIAL_TEMPLATE='ATTR{idVendor}=="%s", ATTR{idProduct}=="%s"'
# The above, but also with a USB specifier, for the one-line-for-all version
UDEV_CONDITION_TEMPLATE="SUBSYSTEM==\"usb\", $UDEV_CONDITION_PARTIAL_TEMPLATE"
# The older syntax for ancient udev. This one is also "partial" since you
# have to specify each device ID separately on this version of udev.
UDEV_CONDITION_OLD_TEMPLATE='SYSFS{idVendor}=="%s", SYSFS{idProduct}=="%s"'

# ACTIONS
# The "action" part of udev rules.

# When using udev-acl, this instructs it to give access to the current
# console user.
UDEV_ACTION='ENV{ID_REMOTE_CONTROL}="1"'
# This says give r/w to the dialout group
UDEV_ACTION_GENERIC='MODE="0660", GROUP="dialout"'
# This says make a symlink in /dev called "harmony-<id>"
UDEV_ACTION_POLICYKIT='SYMLINK+="harmony-%%k"'

# Stuff for HAL. Deprecated.
HAL_PRE_TEMPLATE='    <match key="usb_device.vendor_id" int="0x%s">'
HAL_RULE_TEMPLATE='      <match key="usb_device.product_id" int="0x%s">
        <append key="info.capabilities" type="strlist">access_control</append>
        <merge key="access_control.file"
               type="copy_property">linux.device_file</merge>
        <merge key="access_control.type" type="string">libconcord</merge>
      </match>'
HAL_POST='    </match>'

NATIONAL_VID='0400'
NATIONAL_PID='c359'
LOGITECH_VID='046d'
LOGITECH_MIN_PID='c110'
LOGITECH_MAX_PID='c14f'
LOGITECH_PID_GLOB='c1[1-4][0-9a-f]'

UDEV_FILE='libconcord.rules'
UDEV_USBNET_FILE='libconcord-usbnet.rules'
HAL_POLICY_FILE='libconcord.fdi'
POLICYKIT_FILE='org.freedesktop.hal.device-access.libconcord.policy'
CONSOLEKIT_FILE='libconcord.perms'

#
# GENERAL FUNCTIONS
#
emit_for_all() {
    file="$1"
    template="$2"
    include_vid="$3"

		printf "$template\n" $NATIONAL_VID $NATIONAL_PID >>$file

    for pid in `seq 0x$LOGITECH_MIN_PID 0x$LOGITECH_MAX_PID`; do
        pid=`printf "%x" $pid`
        if [ "$include_vid" == 'yes' ] ; then
            printf "$template\n" $LOGITECH_VID $pid >>$file
        else
            printf "$template\n" $pid >>$file
        fi
    done
}

#
# UDEV FUNCTIONS
#
emit_old_udev_header() {
    file="$1"
    cat >$file <<END
# Neat trick so that non-harmony devices don't read through a million rules
SUBSYSTEM=="usb_device", GOTO="harmony_usb_rules"
SUBSYSTEM=="usb", GOTO="harmony_usb_rules"
SUBSYSTEM!="usb", GOTO="harmony_rules_end"
GOTO="harmony_rules_end"
LABEL="harmony_usb_rules"
END
}

emit_old_udev_footer() {
    file="$1"
    cat >>$file <<END
GOTO="harmony_rules_end"
LABEL="harmony_rules_end"
END
}

emit_udev_rules() {
  file="$1"
  mode="$2"

  template="$UDEV_CONDITION_TEMPLATE"
  if [ "$mode" == 'generic_udev' ] ; then
    template="$template, $UDEV_ACTION_GENERIC"
  else
    template="$template, $UDEV_ACTION"
  fi

  printf "$template\n" $NATIONAL_VID $NATIONAL_PID >>$file
  printf "$template\n" $LOGITECH_VID $LOGITECH_PID_GLOB >>$file
}

# Rule for the usbnet family of remotes to start a DHCP daemon upon detection.
emit_udev_usbnet_rules() {
    file="$1"
    cat >$file <<END
SUBSYSTEM=="net", ENV{ID_VENDOR_ID}=="046d", ENV{ID_MODEL_ID}=="c11f", \
RUN+="start_concordance_dhcpd_wrapper.sh"
END
}

emit_old_udev_rules() {
    file="$1"
    mode="$2"

    case "$mode" in
      policykit|consolekit)
        template="$UDEV_CONDITION_PARTIAL_TEMPLATE, $UDEV_ACTION_POLICYKIT"
        ;;
      old_udev)
        template="$UDEV_CONDITION_OLD_TEMPLATE, $UDEV_ACTION_GENERIC"
        ;;
    esac

    emit_for_all $file "$template" 'yes'
}

create_udev_files() {
    mode=$1

    echo -n "Creating udev file: $UDEV_FILE ... "
    # delete wahtever used to be there
    rm -f $UDEV_FILE

    case "$mode" in
      policykit|consolekit|old_udev)
        emit_old_udev_header $UDEV_FILE
        emit_old_udev_rules $UDEV_FILE $mode
        emit_old_udev_footer $UDEV_FILE
        ;;
      udev|generic_udev)
        emit_udev_rules $UDEV_FILE $mode
        ;;
      *)
        echo "BAD MODE: $mode"
        exit 1
        ;;
    esac
    echo 'done'
    # USBNET rule needs to be in a separate file because it depends on
    # 75-net-description.rules.
    echo -n "Creating udev file: $UDEV_USBNET_FILE ... "
    emit_udev_usbnet_rules $UDEV_USBNET_FILE
    echo 'done'
}

#
# HAL POLICY FUNCITONS
#

emit_hal_policy_header() {
    file="$1"
    cat >$file <<END
<?xml version="1.0" encoding="UTF-8"?>
<deviceinfo version="0.2">
  <device>
END
    printf "$HAL_PRE_TEMPLATE\n" $LOGITECH_VID >>$file
}

emit_hal_policy_footer() {
    printf "$HAL_POST\n" >>$file
    file="$1"
    cat >>$file <<END
  </device>
</deviceinfo>
END
}
    
emit_hal_policy_rules() {
    file="$1"

    emit_for_all $file "$HAL_RULE_TEMPLATE"
}

create_hal_policy_file() {
    echo -n "Creating hal policy file: $HAL_POLICY_FILE ... "
    emit_hal_policy_header $HAL_POLICY_FILE
    emit_hal_policy_rules $HAL_POLICY_FILE
    emit_hal_policy_footer $HAL_POLICY_FILE
    echo 'done'
}

#
# POLICYKIT FUNCITONS
#

emit_policykit() {
    file="$1"
    cat >$file <<END
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policyconfig PUBLIC
 "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">

<policyconfig>
  <action id="org.freedesktop.hal.device-access.libconcord">
    <description>Directly access the Logitech Harmony remote</description>
    <message>System policy prevents access to the Logitech Harmony remote</message>
    <defaults>
      <allow_inactive>no</allow_inactive>
      <allow_active>yes</allow_active>
    </defaults>
  </action>
</policyconfig>
END
}

create_policykit_file() {
    echo -n "Creating PolicyKit file: $POLICYKIT_FILE ..."
    emit_policykit $POLICYKIT_FILE
    echo ' done'
}

#
# CONSOLEKIT
#

emit_perms() {
    file="$1"
    cat >$file <<END
<harmony>=/dev/harmony*
<console> 0600 <harmony> 0600 root
END
}

create_perms_file() {
    echo -n "Creating perms file: $CONSOLEKIT_FILE ..."
    emit_perms $CONSOLEKIT_FILE
    echo ' done'
}

#
# OTHER FUNCTIONS
#

usage() {
    echo "Usage: $0 <-u|-p|-c|-o>"
}

#
# MAIN
#

while getopts cgopu opt; do
    case $opt in
        c)
            MODE='consolekit'
            ;;
        g)
            MODE='generic_udev'
            ;;
        o)
            MODE='old_udev'
            ;;
        p)
            MODE='policykit'
            ;;
        u)
            MODE='udev'
            ;;
        *)
            usage
            exit 1
            ;;
    esac
done

if [ "$MODE" == '' ]; then
    usage
    exit 1
fi

create_udev_files $MODE
if [ "$MODE" == 'policykit' ]; then
    create_hal_policy_file
    create_policykit_file
elif [ "$MODE" == 'consolekit' ]; then
    create_perms_file
fi

