~martijnbraam/thumbdrives

25b2a6e95ea82c3e2f6be1ac26daef91e191e4f9 — Martijn Braam 2 years ago
Initial commit
A  => COPYING +24 -0
@@ 1,24 @@
Copyright (C) 2020 Martijn Braam
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Except as contained in this notice, the name(s) of the above copyright
holders shall not be used in advertising or otherwise to promote the sale,
use or other dealings in this Software without prior written
authorization.

A  => build-aux/meson/postinstall.py +21 -0
@@ 1,21 @@
#!/usr/bin/env python3

from os import environ, path
from subprocess import call

prefix = environ.get('MESON_INSTALL_PREFIX', '/usr/local')
datadir = path.join(prefix, 'share')
destdir = environ.get('DESTDIR', '')

# Package managers set this so we don't need to run
if not destdir:
    print('Updating icon cache...')
    call(['gtk-update-icon-cache', '-qtf', path.join(datadir, 'icons', 'hicolor')])

    print('Updating desktop database...')
    call(['update-desktop-database', '-q', path.join(datadir, 'applications')])

    print('Compiling GSettings schemas...')
    call(['glib-compile-schemas', path.join(datadir, 'glib-2.0', 'schemas')])



A  => data/meson.build +45 -0
@@ 1,45 @@
desktop_file = i18n.merge_file(
  input: 'nl.brixit.Thumbdrives.desktop.in',
  output: 'nl.brixit.Thumbdrives.desktop',
  type: 'desktop',
  po_dir: '../po',
  install: true,
  install_dir: join_paths(get_option('datadir'), 'applications')
)

desktop_utils = find_program('desktop-file-validate', required: false)
if desktop_utils.found()
  test('Validate desktop file', desktop_utils,
    args: [desktop_file]
  )
endif

appstream_file = i18n.merge_file(
  input: 'nl.brixit.Thumbdrives.appdata.xml.in',
  output: 'nl.brixit.Thumbdrives.appdata.xml',
  po_dir: '../po',
  install: true,
  install_dir: join_paths(get_option('datadir'), 'appdata')
)

appstream_util = find_program('appstream-util', required: false)
if appstream_util.found()
  test('Validate appstream file', appstream_util,
    args: ['validate', appstream_file]
  )
endif

install_data('nl.brixit.Thumbdrives.gschema.xml',
  install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas')
)

compile_schemas = find_program('glib-compile-schemas', required: false)
if compile_schemas.found()
  test('Validate schema file', compile_schemas,
    args: ['--strict', '--dry-run', meson.current_source_dir()]
  )
endif

install_data('nl.brixit.Thumbdrives.policy',
  install_dir: join_paths(get_option('datadir'), 'polkit-1/actions')
)

A  => data/nl.brixit.Thumbdrives.appdata.xml.in +8 -0
@@ 1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop">
	<id>nl.brixit.Thumbdrives.desktop</id>
	<metadata_license>CC0-1.0</metadata_license>
	<project_license>MIT</project_license>
	<description>
	</description>
</component>

A  => data/nl.brixit.Thumbdrives.desktop.in +7 -0
@@ 1,7 @@
[Desktop Entry]
Name=thumbdrives
Exec=thumbdrives
Terminal=false
Type=Application
Categories=GTK;
StartupNotify=true

A  => data/nl.brixit.Thumbdrives.gschema.xml +5 -0
@@ 1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<schemalist gettext-domain="thumbdrives">
	<schema id="nl.brixit.Thumbdrives" path="/nl/brixit/Thumbdrives/">
	</schema>
</schemalist>

A  => data/nl.brixit.Thumbdrives.policy +33 -0
@@ 1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policyconfig PUBLIC
 "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
 "http://www.freedesktop.org/software/polkit/policyconfig-1.dtd">
<policyconfig>

  <vendor>Thumbdrives</vendor>

  <action id="nl.brixit.Thumbdrives.pkexec.mount">
    <message>Authentication is required to mount an image</message>
    <icon_name>nl.brixit.Thumbdrives</icon_name>
    <defaults>
      <allow_any>auth_admin</allow_any>
      <allow_inactive>auth_admin</allow_inactive>
      <allow_active>auth_admin</allow_active>
    </defaults>
    <annotate key="org.freedesktop.policykit.exec.path">/usr/bin/pk-thumbdrive-action</annotate>
    <annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
  </action>

    <action id="nl.brixit.Thumbdrives.pkexec.umount">
    <message>Authentication is required to unmount an image</message>
    <icon_name>nl.brixit.Thumbdrives</icon_name>
    <defaults>
      <allow_any>auth_admin</allow_any>
      <allow_inactive>auth_admin</allow_inactive>
      <allow_active>auth_admin</allow_active>
    </defaults>
    <annotate key="org.freedesktop.policykit.exec.path">/usr/bin/pk-thumbdrive-action</annotate>
    <annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
  </action>

</policyconfig>

A  => meson.build +15 -0
@@ 1,15 @@
project('thumbdrives',  
          version: '0.1.0',
    meson_version: '>= 0.50.0',
  default_options: [ 'warning_level=2',
                   ],
)

i18n = import('i18n')


subdir('data')
subdir('src')
subdir('po')

meson.add_install_script('build-aux/meson/postinstall.py')
\ No newline at end of file

A  => nl.brixit.Thumbdrives.json +37 -0
@@ 1,37 @@
{
    "app-id" : "nl.brixit.Thumbdrives",
    "runtime" : "org.gnome.Platform",
    "runtime-version" : "3.34",
    "sdk" : "org.gnome.Sdk",
    "command" : "thumbdrives",
    "finish-args" : [
        "--share=network",
        "--share=ipc",
        "--socket=fallback-x11",
        "--socket=wayland"
    ],
    "cleanup" : [
        "/include",
        "/lib/pkgconfig",
        "/man",
        "/share/doc",
        "/share/gtk-doc",
        "/share/man",
        "/share/pkgconfig",
        "*.la",
        "*.a"
    ],
    "modules" : [
        {
            "name" : "thumbdrives",
            "builddir" : true,
            "buildsystem" : "meson",
            "sources" : [
                {
                    "type" : "git",
                    "url" : "file:///home/martijn/Projects/Thumbdrives"
                }
            ]
        }
    ]
}

A  => po/LINGUAS +0 -0
A  => po/POTFILES +7 -0
@@ 1,7 @@
data/nl.brixit.Thumbdrives.desktop.in
data/nl.brixit.Thumbdrives.appdata.xml.in
data/nl.brixit.Thumbdrives.gschema.xml
src/window.ui
src/main.py
src/window.py


A  => po/meson.build +1 -0
@@ 1,1 @@
i18n.gettext('thumbdrives', preset: 'glib')

A  => src/__init__.py +0 -0
A  => src/main.py +53 -0
@@ 1,53 @@
# main.py
#
# Copyright 2020 Martijn Braam
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Except as contained in this notice, the name(s) of the above copyright
# holders shall not be used in advertising or otherwise to promote the sale,
# use or other dealings in this Software without prior written
# authorization.

import sys
import gi

gi.require_version('Gtk', '3.0')

from gi.repository import Gtk, Gio

from .window import ThumbdrivesWindow


class Application(Gtk.Application):
    def __init__(self):
        super().__init__(application_id='nl.brixit.Thumbdrives',
                         flags=Gio.ApplicationFlags.FLAGS_NONE)

    def do_activate(self):
        win = self.props.active_window
        if not win:
            win = ThumbdrivesWindow(application=self)
        win.present()


def main(version):
    app = Application()
    return app.run(sys.argv)

A  => src/meson.build +36 -0
@@ 1,36 @@
pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())
moduledir = join_paths(pkgdatadir, 'thumbdrives')
gnome = import('gnome')

gnome.compile_resources('thumbdrives',
  'thumbdrives.gresource.xml',
  gresource_bundle: true,
  install: true,
  install_dir: pkgdatadir,
)

python = import('python')

conf = configuration_data()
conf.set('PYTHON', python.find_installation('python3').path())
conf.set('VERSION', meson.project_version())
conf.set('localedir', join_paths(get_option('prefix'), get_option('localedir')))
conf.set('pkgdatadir', pkgdatadir)

configure_file(
  input: 'thumbdrives.in',
  output: 'thumbdrives',
  configuration: conf,
  install: true,
  install_dir: get_option('bindir')
)

thumbdrives_sources = [
  '__init__.py',
  'main.py',
  'vdisk.py',
  'window.py',
]

install_data(thumbdrives_sources, install_dir: moduledir)
install_data('pk-thumbdrive-action.sh', install_dir: get_option('bindir'), install_mode: 'rwxr-xr-x', rename: ['pk-thumbdrive-action'])

A  => src/pk-thumbdrive-action.sh +113 -0
@@ 1,113 @@
#!/bin/sh
ACTION=$1
BACKING_FILE=$2
CONFIGFS=/sys/kernel/config/usb_gadget
GADGET=$CONFIGFS/thumbdrives

remove_gadget () {
	local gadget=$1

	# Disable the gadget
	echo "" > "$gadget"/UDC

	# Remove the functions from the config
	for function in "$gadget"/configs/*/*.*
	do
		rm "$function"
	done

	# Remove the language data from the config
	for lang in "$gadget"/configs/*/strings/*
	do
		rmdir "$lang"
	done

	# Remove the configurations
	for config in "$gadget"/configs/*/
	do
		rmdir "$config"
	done

	# Remove the defined functions
	for function in "$gadget"/functions/*/
	do
		rmdir "$function"
	done

	# Remove the defined language data
	for lang in "$gadget"/strings/*
	do
		rmdir "$lang"
	done

	# Remove the gadget
	rmdir "$gadget"
}

disable_existing_gadgets () {
	for gadget in "$CONFIGFS"/*/UDC
	do
		echo "" > "$gadget"
	done
}

create_gadget () {
	local backing="$1"
	mkdir $GADGET
	echo "0x1209" > $GADGET/idVendor # Generic
	echo "0x4202" > $GADGET/idProduct # Random id

	# English locale
	LOCALE=$GADGET/strings/0x409
	mkdir $LOCALE || echo "Could not create $LOCALE"
	echo "Phone" > $LOCALE/manufacturer
	echo "BLEH" > $LOCALE/product
	echo "Thumbdrives" > $LOCALE/serialnumber

	# Mass storage function
	FUNCTION=$GADGET/functions/mass_storage.0
	LUN=$FUNCTION/lun.1
	mkdir $FUNCTION || echo "Could not create $FUNCTION"
	mkdir $LUN || echo "Could not create $LUN"

	# Configuration instance
	CONFIG=$GADGET/configs/c.1
	LOCALE=$CONFIG/strings/0x409
	mkdir $CONFIG || echo "Coud not create $CONFIG"
	mkdir $LOCALE || echo "Coud not create $LOCALE"
	echo "Thumbdrive" > $LOCALE/configuration

	# Link mass storage gadget to backing file
	echo $backing > $LUN/file

	# Mass storage hardware name
	echo "Thumbdrives" > $LUN/inquiry_string

	# Add mass storage to the configuration
	ln -s $FUNCTION $CONFIG

	# Link to controller
	echo "$(ls /sys/class/udc)" > $GADGET/UDC || ( echo "Couldn't write to UDC" )
}


if [ "$ACTION" = "mount-mass-storage" ]
then

	[ -d $GADGET ] && remove_gadget $GADGET
	disable_existing_gadgets
	create_gadget "$BACKING_FILE"
fi

if [ "$ACTION" = "mount-iso" ]
then

	[ -d $GADGET ] && remove_gadget $GADGET
	disable_existing_gadgets
	create_gadget "$BACKING_FILE"
fi

if [ "$ACTION" = "umount" ]
then
	[ -d $GADGET ] && remove_gadget $GADGET
fi

A  => src/thumbdrives.gresource.xml +6 -0
@@ 1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
  <gresource prefix="/nl/brixit/Thumbdrives">
    <file>window.ui</file>
  </gresource>
</gresources>

A  => src/thumbdrives.in +52 -0
@@ 1,52 @@
#!@PYTHON@

# thumbdrives.in
#
# Copyright 2020 Martijn Braam
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Except as contained in this notice, the name(s) of the above copyright
# holders shall not be used in advertising or otherwise to promote the sale,
# use or other dealings in this Software without prior written
# authorization.

import os
import sys
import signal
import gettext

VERSION = '@VERSION@'
pkgdatadir = '@pkgdatadir@'
localedir = '@localedir@'

sys.path.insert(1, pkgdatadir)
signal.signal(signal.SIGINT, signal.SIG_DFL)
gettext.install('thumbdrives', localedir)

if __name__ == '__main__':
    import gi

    from gi.repository import Gio
    resource = Gio.Resource.load(os.path.join(pkgdatadir, 'thumbdrives.gresource'))
    resource._register()

    from thumbdrives import main
    sys.exit(main.main(VERSION))

A  => src/vdisk.py +11 -0
@@ 1,11 @@
import subprocess

def mount(backing_file, cdimage=False):
    action = 'mount-mass-storage'
    if cdimage:
        action = 'mount-iso'

    subprocess.run(['pkexec', 'pk-thumbdrive-action', action, backing_file])

def unmount():
    subprocess.run(['pkexec', 'pk-thumbdrive-action', 'umount'])

A  => src/window.py +49 -0
@@ 1,49 @@
# window.py
#
# Copyright 2020 Martijn Braam
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Except as contained in this notice, the name(s) of the above copyright
# holders shall not be used in advertising or otherwise to promote the sale,
# use or other dealings in this Software without prior written
# authorization.

from gi.repository import Gtk
import thumbdrives.vdisk as vdisk


@Gtk.Template(resource_path='/nl/brixit/Thumbdrives/window.ui')
class ThumbdrivesWindow(Gtk.ApplicationWindow):
    __gtype_name__ = 'ThumbdrivesWindow'

    mount = Gtk.Template.Child()
    unmount = Gtk.Template.Child()

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def on_mount_clicked(self, widget, args):
        print("MOUNTING")
        subprocess.run(['truncate', '-s', '16G', '/tmp/test.img'])
        vdisk.mount("/tmp/test.img")

    def on_unmount_clicked(self, widget, args):
        vdisk.unmount()

A  => src/window.ui +56 -0
@@ 1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.2 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <template class="ThumbdrivesWindow" parent="GtkApplicationWindow">
    <property name="can_focus">False</property>
    <property name="default_width">600</property>
    <property name="default_height">300</property>
    <child type="titlebar">
      <object class="GtkHeaderBar" id="header_bar">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="title">Hello, World!</property>
        <property name="show_close_button">True</property>
      </object>
    </child>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkButton" id="mount">
            <property name="label" translatable="yes">Mount</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <signal name="clicked" handler="on_mount_clicked" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkButton" id="unmount">
            <property name="label" translatable="yes">Unmount</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <signal name="clicked" handler="on_unmount_clicked" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
        <child>
          <placeholder/>
        </child>
      </object>
    </child>
  </template>
</interface>